2018-07-18 09:37:09 +00:00
package command
import (
2020-02-24 22:34:14 +00:00
"context"
2023-06-04 21:27:56 +00:00
"crypto/tls"
2023-10-13 16:02:24 +00:00
"crypto/x509"
2020-01-29 17:09:55 +00:00
"fmt"
2023-10-13 16:02:24 +00:00
"io/ioutil"
2023-06-26 23:22:45 +00:00
"net"
"net/http"
"os"
"runtime"
2023-12-21 00:21:11 +00:00
"strings"
2023-06-26 23:22:45 +00:00
"time"
2022-07-29 07:17:28 +00:00
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
2023-06-04 21:27:56 +00:00
"google.golang.org/grpc/credentials/tls/certprovider"
"google.golang.org/grpc/credentials/tls/certprovider/pemfile"
2022-05-15 07:43:37 +00:00
"google.golang.org/grpc/reflection"
2018-07-18 09:37:09 +00:00
2022-07-29 07:17:28 +00:00
"github.com/seaweedfs/seaweedfs/weed/pb"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/s3_pb"
"github.com/seaweedfs/seaweedfs/weed/security"
2019-06-05 08:30:24 +00:00
2020-01-29 17:09:55 +00:00
"github.com/gorilla/mux"
2019-06-05 08:30:24 +00:00
2022-07-29 07:17:28 +00:00
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/s3api"
stats_collect "github.com/seaweedfs/seaweedfs/weed/stats"
"github.com/seaweedfs/seaweedfs/weed/util"
2018-07-18 09:37:09 +00:00
)
var (
2019-04-24 07:18:01 +00:00
s3StandaloneOptions S3Options
2018-07-18 09:37:09 +00:00
)
type S3Options struct {
2022-03-30 17:46:13 +00:00
filer * string
bindIp * string
port * int
2023-06-14 06:58:49 +00:00
portHttps * int
2022-05-15 07:43:37 +00:00
portGrpc * int
2022-03-30 17:46:13 +00:00
config * string
domainName * string
2023-12-21 00:21:11 +00:00
allowedOrigins * string
2022-03-30 17:46:13 +00:00
tlsPrivateKey * string
tlsCertificate * string
2023-10-13 16:02:24 +00:00
tlsCACertificate * string
tlsVerifyClientCert * bool
2022-03-30 17:46:13 +00:00
metricsHttpPort * int
allowEmptyFolder * bool
allowDeleteBucketNotEmpty * bool
auditLogConfig * string
localFilerSocket * string
2022-08-05 00:35:00 +00:00
dataCenter * string
2023-06-26 23:22:45 +00:00
localSocket * string
2023-06-04 21:27:56 +00:00
certProvider certprovider . Provider
2018-07-18 09:37:09 +00:00
}
func init ( ) {
cmdS3 . Run = runS3 // break init cycle
2019-04-24 07:18:01 +00:00
s3StandaloneOptions . filer = cmdS3 . Flag . String ( "filer" , "localhost:8888" , "filer server address" )
2022-03-11 22:02:39 +00:00
s3StandaloneOptions . bindIp = cmdS3 . Flag . String ( "ip.bind" , "" , "ip address to bind to. Default to localhost." )
2019-04-24 07:18:01 +00:00
s3StandaloneOptions . port = cmdS3 . Flag . Int ( "port" , 8333 , "s3 server http listen port" )
2023-06-14 06:58:49 +00:00
s3StandaloneOptions . portHttps = cmdS3 . Flag . Int ( "port.https" , 0 , "s3 server https listen port" )
2022-05-15 07:43:37 +00:00
s3StandaloneOptions . portGrpc = cmdS3 . Flag . Int ( "port.grpc" , 0 , "s3 server grpc listen port" )
2020-10-22 06:23:00 +00:00
s3StandaloneOptions . domainName = cmdS3 . Flag . String ( "domainName" , "" , "suffix of the host name in comma separated list, {bucket}.{domainName}" )
2023-12-21 00:21:11 +00:00
s3StandaloneOptions . allowedOrigins = cmdS3 . Flag . String ( "allowedOrigins" , "*" , "comma separated list of allowed origins" )
2022-08-05 00:35:00 +00:00
s3StandaloneOptions . dataCenter = cmdS3 . Flag . String ( "dataCenter" , "" , "prefer to read and write to volumes in this data center" )
2020-02-09 22:30:02 +00:00
s3StandaloneOptions . config = cmdS3 . Flag . String ( "config" , "" , "path to the config file" )
2021-12-07 13:20:52 +00:00
s3StandaloneOptions . auditLogConfig = cmdS3 . Flag . String ( "auditLogConfig" , "" , "path to the audit log config file" )
2019-04-24 07:18:01 +00:00
s3StandaloneOptions . tlsPrivateKey = cmdS3 . Flag . String ( "key.file" , "" , "path to the TLS private key file" )
s3StandaloneOptions . tlsCertificate = cmdS3 . Flag . String ( "cert.file" , "" , "path to the TLS certificate file" )
2023-10-13 16:02:24 +00:00
s3StandaloneOptions . tlsCACertificate = cmdS3 . Flag . String ( "cacert.file" , "" , "path to the TLS CA certificate file" )
s3StandaloneOptions . tlsVerifyClientCert = cmdS3 . Flag . Bool ( "tlsVerifyClientCert" , false , "whether to verify the client's certificate" )
2020-09-24 12:45:39 +00:00
s3StandaloneOptions . metricsHttpPort = cmdS3 . Flag . Int ( "metricsPort" , 0 , "Prometheus metrics listen port" )
2021-09-27 05:34:14 +00:00
s3StandaloneOptions . allowEmptyFolder = cmdS3 . Flag . Bool ( "allowEmptyFolder" , true , "allow empty folders" )
2022-03-30 17:46:13 +00:00
s3StandaloneOptions . allowDeleteBucketNotEmpty = cmdS3 . Flag . Bool ( "allowDeleteBucketNotEmpty" , true , "allow recursive deleting all entries along with bucket" )
2022-09-14 13:33:00 +00:00
s3StandaloneOptions . localFilerSocket = cmdS3 . Flag . String ( "localFilerSocket" , "" , "local filer socket path" )
2023-06-26 23:22:45 +00:00
s3StandaloneOptions . localSocket = cmdS3 . Flag . String ( "localSocket" , "" , "default to /tmp/seaweedfs-s3-<port>.sock" )
2018-07-18 09:37:09 +00:00
}
var cmdS3 = & Command {
2020-02-09 22:30:02 +00:00
UsageLine : "s3 [-port=8333] [-filer=<ip:port>] [-config=</path/to/config.json>]" ,
2018-07-18 09:37:09 +00:00
Short : "start a s3 API compatible server that is backed by a filer" ,
Long : ` start a s3 API compatible server that is backed by a filer .
2020-02-09 22:30:02 +00:00
By default , you can use any access key and secret key to access the S3 APIs .
To enable credential based access , create a config . json file similar to this :
{
"identities" : [
{
2020-10-08 17:11:59 +00:00
"name" : "anonymous" ,
"actions" : [
"Read"
]
} ,
{
"name" : "some_admin_user" ,
2020-02-09 22:30:02 +00:00
"credentials" : [
{
"accessKey" : "some_access_key1" ,
2020-02-10 00:02:05 +00:00
"secretKey" : "some_secret_key1"
2020-02-09 22:30:02 +00:00
}
] ,
"actions" : [
"Admin" ,
"Read" ,
2020-10-08 17:11:59 +00:00
"List" ,
"Tagging" ,
2020-02-09 22:30:02 +00:00
"Write"
]
} ,
{
"name" : "some_read_only_user" ,
"credentials" : [
{
2020-02-10 00:02:05 +00:00
"accessKey" : "some_access_key2" ,
"secretKey" : "some_secret_key2"
2020-02-09 22:30:02 +00:00
}
] ,
"actions" : [
"Read"
]
} ,
{
"name" : "some_normal_user" ,
"credentials" : [
{
2020-02-10 00:02:05 +00:00
"accessKey" : "some_access_key3" ,
"secretKey" : "some_secret_key3"
2020-02-09 22:30:02 +00:00
}
] ,
"actions" : [
"Read" ,
2020-10-08 17:11:59 +00:00
"List" ,
"Tagging" ,
2020-02-09 22:30:02 +00:00
"Write"
]
2020-02-23 05:34:18 +00:00
} ,
{
"name" : "user_limited_to_bucket1" ,
"credentials" : [
{
"accessKey" : "some_access_key4" ,
"secretKey" : "some_secret_key4"
}
] ,
"actions" : [
"Read:bucket1" ,
2020-10-08 17:11:59 +00:00
"List:bucket1" ,
"Tagging:bucket1" ,
2020-02-23 05:34:18 +00:00
"Write:bucket1"
]
2020-02-09 22:30:02 +00:00
}
]
}
2018-07-18 09:37:09 +00:00
` ,
}
func runS3 ( cmd * Command , args [ ] string ) bool {
2019-06-05 08:30:24 +00:00
util . LoadConfiguration ( "security" , false )
2019-02-18 20:11:52 +00:00
2022-11-24 18:22:59 +00:00
go stats_collect . StartMetricsServer ( * s3StandaloneOptions . bindIp , * s3StandaloneOptions . metricsHttpPort )
2020-09-24 17:21:23 +00:00
2019-04-24 07:18:01 +00:00
return s3StandaloneOptions . startS3Server ( )
}
2023-06-04 21:27:56 +00:00
// GetCertificateWithUpdate Auto refreshing TSL certificate
func ( S3opt * S3Options ) GetCertificateWithUpdate ( * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
certs , err := S3opt . certProvider . KeyMaterial ( context . Background ( ) )
return & certs . Certs [ 0 ] , err
}
2019-04-24 07:18:01 +00:00
func ( s3opt * S3Options ) startS3Server ( ) bool {
2021-09-13 05:47:52 +00:00
filerAddress := pb . ServerAddress ( * s3opt . filer )
2018-07-18 09:37:09 +00:00
2020-02-24 22:34:14 +00:00
filerBucketsPath := "/buckets"
2023-05-16 16:39:43 +00:00
filerGroup := ""
2020-02-24 22:34:14 +00:00
grpcDialOption := security . LoadClientTLS ( util . GetViper ( ) , "grpc.client" )
2020-09-17 13:56:15 +00:00
// metrics read from the filer
var metricsAddress string
var metricsIntervalSec int
2020-02-27 00:49:47 +00:00
for {
2023-01-20 09:48:12 +00:00
err := pb . WithGrpcFilerClient ( false , 0 , filerAddress , grpcDialOption , func ( client filer_pb . SeaweedFilerClient ) error {
2020-02-27 00:49:47 +00:00
resp , err := client . GetFilerConfiguration ( context . Background ( ) , & filer_pb . GetFilerConfigurationRequest { } )
if err != nil {
2021-09-13 05:47:52 +00:00
return fmt . Errorf ( "get filer %s configuration: %v" , filerAddress , err )
2020-02-27 00:49:47 +00:00
}
filerBucketsPath = resp . DirBuckets
2023-05-16 16:39:43 +00:00
filerGroup = resp . FilerGroup
2020-09-17 13:56:15 +00:00
metricsAddress , metricsIntervalSec = resp . MetricsAddress , int ( resp . MetricsIntervalSec )
2020-02-27 00:49:47 +00:00
glog . V ( 0 ) . Infof ( "S3 read filer buckets dir: %s" , filerBucketsPath )
return nil
} )
2020-02-24 22:34:14 +00:00
if err != nil {
2021-09-13 05:47:52 +00:00
glog . V ( 0 ) . Infof ( "wait to connect to filer %s grpc address %s" , * s3opt . filer , filerAddress . ToGrpcAddress ( ) )
2020-02-27 00:49:47 +00:00
time . Sleep ( time . Second )
} else {
2021-09-13 05:47:52 +00:00
glog . V ( 0 ) . Infof ( "connected to filer %s grpc address %s" , * s3opt . filer , filerAddress . ToGrpcAddress ( ) )
2020-02-27 00:49:47 +00:00
break
2020-02-24 22:34:14 +00:00
}
}
2020-09-19 07:03:00 +00:00
2020-09-24 17:21:23 +00:00
go stats_collect . LoopPushingMetric ( "s3" , stats_collect . SourceName ( uint32 ( * s3opt . port ) ) , metricsAddress , metricsIntervalSec )
2020-02-24 22:34:14 +00:00
2018-07-18 09:37:09 +00:00
router := mux . NewRouter ( ) . SkipClean ( true )
2022-09-06 15:20:23 +00:00
var localFilerSocket string
if s3opt . localFilerSocket != nil {
localFilerSocket = * s3opt . localFilerSocket
}
2022-05-15 07:43:37 +00:00
s3ApiServer , s3ApiServer_err := s3api . NewS3ApiServer ( router , & s3api . S3ApiServerOption {
2022-03-30 17:46:13 +00:00
Filer : filerAddress ,
Port : * s3opt . port ,
Config : * s3opt . config ,
DomainName : * s3opt . domainName ,
2023-12-21 00:21:11 +00:00
AllowedOrigins : strings . Split ( * s3opt . allowedOrigins , "," ) ,
2022-03-30 17:46:13 +00:00
BucketsPath : filerBucketsPath ,
GrpcDialOption : grpcDialOption ,
AllowEmptyFolder : * s3opt . allowEmptyFolder ,
AllowDeleteBucketNotEmpty : * s3opt . allowDeleteBucketNotEmpty ,
2022-09-06 15:20:23 +00:00
LocalFilerSocket : localFilerSocket ,
2022-08-05 00:35:00 +00:00
DataCenter : * s3opt . dataCenter ,
2023-05-16 16:39:43 +00:00
FilerGroup : filerGroup ,
2018-07-18 09:37:09 +00:00
} )
if s3ApiServer_err != nil {
glog . Fatalf ( "S3 API Server startup error: %v" , s3ApiServer_err )
}
2020-09-24 12:48:39 +00:00
2018-07-23 04:28:54 +00:00
httpS := & http . Server { Handler : router }
2022-05-15 07:43:37 +00:00
if * s3opt . portGrpc == 0 {
* s3opt . portGrpc = 10000 + * s3opt . port
}
2022-03-11 22:02:39 +00:00
if * s3opt . bindIp == "" {
* s3opt . bindIp = "localhost"
}
2023-06-26 23:22:45 +00:00
if runtime . GOOS != "windows" {
localSocket := * s3opt . localSocket
if localSocket == "" {
localSocket = fmt . Sprintf ( "/tmp/seaweedfs-s3-%d.sock" , * s3opt . port )
}
if err := os . Remove ( localSocket ) ; err != nil && ! os . IsNotExist ( err ) {
glog . Fatalf ( "Failed to remove %s, error: %s" , localSocket , err . Error ( ) )
}
go func ( ) {
// start on local unix socket
s3SocketListener , err := net . Listen ( "unix" , localSocket )
if err != nil {
glog . Fatalf ( "Failed to listen on %s: %v" , localSocket , err )
}
httpS . Serve ( s3SocketListener )
} ( )
}
2021-12-17 19:34:37 +00:00
listenAddress := fmt . Sprintf ( "%s:%d" , * s3opt . bindIp , * s3opt . port )
2022-09-14 18:59:55 +00:00
s3ApiListener , s3ApiLocalListener , err := util . NewIpAndLocalListeners ( * s3opt . bindIp , * s3opt . port , time . Duration ( 10 ) * time . Second )
2018-07-23 04:28:54 +00:00
if err != nil {
glog . Fatalf ( "S3 API Server listener on %s error: %v" , listenAddress , err )
2018-07-18 09:37:09 +00:00
}
2021-12-07 13:20:52 +00:00
if len ( * s3opt . auditLogConfig ) > 0 {
s3err . InitAuditLog ( * s3opt . auditLogConfig )
2021-12-10 14:40:32 +00:00
if s3err . Logger != nil {
defer s3err . Logger . Close ( )
}
2021-12-07 13:20:52 +00:00
}
2022-05-15 07:43:37 +00:00
// starting grpc server
grpcPort := * s3opt . portGrpc
grpcL , grpcLocalL , err := util . NewIpAndLocalListeners ( * s3opt . bindIp , grpcPort , 0 )
if err != nil {
glog . Fatalf ( "s3 failed to listen on grpc port %d: %v" , grpcPort , err )
}
grpcS := pb . NewGrpcServer ( security . LoadServerTLS ( util . GetViper ( ) , "grpc.s3" ) )
s3_pb . RegisterSeaweedS3Server ( grpcS , s3ApiServer )
reflection . Register ( grpcS )
if grpcLocalL != nil {
go grpcS . Serve ( grpcLocalL )
}
go grpcS . Serve ( grpcL )
2019-04-24 07:18:01 +00:00
if * s3opt . tlsPrivateKey != "" {
2023-06-04 21:27:56 +00:00
pemfileOptions := pemfile . Options {
CertFile : * s3opt . tlsCertificate ,
KeyFile : * s3opt . tlsPrivateKey ,
RefreshDuration : security . CredRefreshingInterval ,
}
if s3opt . certProvider , err = pemfile . NewProvider ( pemfileOptions ) ; err != nil {
glog . Fatalf ( "pemfile.NewProvider(%v) failed: %v" , pemfileOptions , err )
}
2023-10-13 16:02:24 +00:00
caCertPool := x509 . NewCertPool ( )
if * s3Options . tlsCACertificate != "" {
// load CA certificate file and add it to list of client CAs
caCertFile , err := ioutil . ReadFile ( * s3opt . tlsCACertificate )
if err != nil {
glog . Fatalf ( "error reading CA certificate: %v" , err )
}
caCertPool . AppendCertsFromPEM ( caCertFile )
}
clientAuth := tls . NoClientCert
if * s3Options . tlsVerifyClientCert {
clientAuth = tls . RequireAndVerifyClientCert
}
httpS . TLSConfig = & tls . Config {
GetCertificate : s3opt . GetCertificateWithUpdate ,
ClientAuth : clientAuth ,
ClientCAs : caCertPool ,
}
2023-06-14 06:58:49 +00:00
if * s3opt . portHttps == 0 {
glog . V ( 0 ) . Infof ( "Start Seaweed S3 API Server %s at https port %d" , util . Version ( ) , * s3opt . port )
if s3ApiLocalListener != nil {
go func ( ) {
if err = httpS . ServeTLS ( s3ApiLocalListener , "" , "" ) ; err != nil {
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
} ( )
}
if err = httpS . ServeTLS ( s3ApiListener , "" , "" ) ; err != nil {
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
} else {
glog . V ( 0 ) . Infof ( "Start Seaweed S3 API Server %s at https port %d" , util . Version ( ) , * s3opt . portHttps )
s3ApiListenerHttps , s3ApiLocalListenerHttps , _ := util . NewIpAndLocalListeners (
* s3opt . bindIp , * s3opt . portHttps , time . Duration ( 10 ) * time . Second )
if s3ApiLocalListenerHttps != nil {
go func ( ) {
if err = httpS . ServeTLS ( s3ApiLocalListenerHttps , "" , "" ) ; err != nil {
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
} ( )
}
2022-03-16 05:28:18 +00:00
go func ( ) {
2023-06-14 06:58:49 +00:00
if err = httpS . ServeTLS ( s3ApiListenerHttps , "" , "" ) ; err != nil {
2022-03-16 05:28:18 +00:00
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
} ( )
}
2023-06-14 06:58:49 +00:00
}
if * s3opt . tlsPrivateKey == "" || * s3opt . portHttps > 0 {
2020-06-02 07:10:35 +00:00
glog . V ( 0 ) . Infof ( "Start Seaweed S3 API Server %s at http port %d" , util . Version ( ) , * s3opt . port )
2022-09-14 18:59:55 +00:00
if s3ApiLocalListener != nil {
2022-03-16 05:28:18 +00:00
go func ( ) {
2022-09-14 18:59:55 +00:00
if err = httpS . Serve ( s3ApiLocalListener ) ; err != nil {
2022-03-16 05:28:18 +00:00
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
} ( )
}
2018-07-23 04:28:54 +00:00
if err = httpS . Serve ( s3ApiListener ) ; err != nil {
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
2018-07-18 09:37:09 +00:00
}
return true
}