2018-07-18 17:37:09 +08:00
package command
import (
2020-02-25 06:34:14 +08:00
"context"
2023-06-05 05:27:56 +08:00
"crypto/tls"
2023-10-14 00:02:24 +08:00
"crypto/x509"
2020-01-30 01:09:55 +08:00
"fmt"
2023-10-14 00:02:24 +08:00
"io/ioutil"
2023-06-27 07:22:45 +08:00
"net"
"net/http"
"os"
"runtime"
2023-12-21 08:21:11 +08:00
"strings"
2023-06-27 07:22:45 +08:00
"time"
2022-07-29 15:17:28 +08:00
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
2023-06-05 05:27:56 +08:00
"google.golang.org/grpc/credentials/tls/certprovider"
"google.golang.org/grpc/credentials/tls/certprovider/pemfile"
2022-05-15 15:43:37 +08:00
"google.golang.org/grpc/reflection"
2018-07-18 17:37:09 +08:00
2022-07-29 15:17:28 +08: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 16:30:24 +08:00
2020-01-30 01:09:55 +08:00
"github.com/gorilla/mux"
2019-06-05 16:30:24 +08:00
2022-07-29 15:17:28 +08: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 17:37:09 +08:00
)
var (
2019-04-24 15:18:01 +08:00
s3StandaloneOptions S3Options
2018-07-18 17:37:09 +08:00
)
type S3Options struct {
2022-03-31 01:46:13 +08:00
filer * string
bindIp * string
port * int
2023-06-14 14:58:49 +08:00
portHttps * int
2022-05-15 15:43:37 +08:00
portGrpc * int
2022-03-31 01:46:13 +08:00
config * string
domainName * string
2023-12-21 08:21:11 +08:00
allowedOrigins * string
2022-03-31 01:46:13 +08:00
tlsPrivateKey * string
tlsCertificate * string
2023-10-14 00:02:24 +08:00
tlsCACertificate * string
tlsVerifyClientCert * bool
2022-03-31 01:46:13 +08:00
metricsHttpPort * int
2024-07-13 01:56:26 +08:00
metricsHttpIp * string
2022-03-31 01:46:13 +08:00
allowEmptyFolder * bool
allowDeleteBucketNotEmpty * bool
auditLogConfig * string
localFilerSocket * string
2022-08-05 08:35:00 +08:00
dataCenter * string
2023-06-27 07:22:45 +08:00
localSocket * string
2023-06-05 05:27:56 +08:00
certProvider certprovider . Provider
2018-07-18 17:37:09 +08:00
}
func init ( ) {
cmdS3 . Run = runS3 // break init cycle
2019-04-24 15:18:01 +08:00
s3StandaloneOptions . filer = cmdS3 . Flag . String ( "filer" , "localhost:8888" , "filer server address" )
2022-03-12 06:02:39 +08:00
s3StandaloneOptions . bindIp = cmdS3 . Flag . String ( "ip.bind" , "" , "ip address to bind to. Default to localhost." )
2019-04-24 15:18:01 +08:00
s3StandaloneOptions . port = cmdS3 . Flag . Int ( "port" , 8333 , "s3 server http listen port" )
2023-06-14 14:58:49 +08:00
s3StandaloneOptions . portHttps = cmdS3 . Flag . Int ( "port.https" , 0 , "s3 server https listen port" )
2022-05-15 15:43:37 +08:00
s3StandaloneOptions . portGrpc = cmdS3 . Flag . Int ( "port.grpc" , 0 , "s3 server grpc listen port" )
2020-10-22 14:23:00 +08:00
s3StandaloneOptions . domainName = cmdS3 . Flag . String ( "domainName" , "" , "suffix of the host name in comma separated list, {bucket}.{domainName}" )
2023-12-21 08:21:11 +08:00
s3StandaloneOptions . allowedOrigins = cmdS3 . Flag . String ( "allowedOrigins" , "*" , "comma separated list of allowed origins" )
2022-08-05 08:35:00 +08:00
s3StandaloneOptions . dataCenter = cmdS3 . Flag . String ( "dataCenter" , "" , "prefer to read and write to volumes in this data center" )
2020-02-10 06:30:02 +08:00
s3StandaloneOptions . config = cmdS3 . Flag . String ( "config" , "" , "path to the config file" )
2021-12-07 21:20:52 +08:00
s3StandaloneOptions . auditLogConfig = cmdS3 . Flag . String ( "auditLogConfig" , "" , "path to the audit log config file" )
2019-04-24 15:18:01 +08: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-14 00:02:24 +08: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 20:45:39 +08:00
s3StandaloneOptions . metricsHttpPort = cmdS3 . Flag . Int ( "metricsPort" , 0 , "Prometheus metrics listen port" )
2024-07-13 01:56:26 +08:00
s3StandaloneOptions . metricsHttpIp = cmdS3 . Flag . String ( "metricsIp" , "" , "metrics listen ip. If empty, default to same as -ip.bind option." )
2021-09-27 13:34:14 +08:00
s3StandaloneOptions . allowEmptyFolder = cmdS3 . Flag . Bool ( "allowEmptyFolder" , true , "allow empty folders" )
2022-03-31 01:46:13 +08:00
s3StandaloneOptions . allowDeleteBucketNotEmpty = cmdS3 . Flag . Bool ( "allowDeleteBucketNotEmpty" , true , "allow recursive deleting all entries along with bucket" )
2022-09-14 21:33:00 +08:00
s3StandaloneOptions . localFilerSocket = cmdS3 . Flag . String ( "localFilerSocket" , "" , "local filer socket path" )
2023-06-27 07:22:45 +08:00
s3StandaloneOptions . localSocket = cmdS3 . Flag . String ( "localSocket" , "" , "default to /tmp/seaweedfs-s3-<port>.sock" )
2018-07-18 17:37:09 +08:00
}
var cmdS3 = & Command {
2020-02-10 06:30:02 +08:00
UsageLine : "s3 [-port=8333] [-filer=<ip:port>] [-config=</path/to/config.json>]" ,
2018-07-18 17:37:09 +08: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-10 06:30:02 +08: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-09 01:11:59 +08:00
"name" : "anonymous" ,
"actions" : [
"Read"
]
} ,
{
"name" : "some_admin_user" ,
2020-02-10 06:30:02 +08:00
"credentials" : [
{
"accessKey" : "some_access_key1" ,
2020-02-10 08:02:05 +08:00
"secretKey" : "some_secret_key1"
2020-02-10 06:30:02 +08:00
}
] ,
"actions" : [
"Admin" ,
"Read" ,
2020-10-09 01:11:59 +08:00
"List" ,
"Tagging" ,
2020-02-10 06:30:02 +08:00
"Write"
]
} ,
{
"name" : "some_read_only_user" ,
"credentials" : [
{
2020-02-10 08:02:05 +08:00
"accessKey" : "some_access_key2" ,
"secretKey" : "some_secret_key2"
2020-02-10 06:30:02 +08:00
}
] ,
"actions" : [
"Read"
]
} ,
{
"name" : "some_normal_user" ,
"credentials" : [
{
2020-02-10 08:02:05 +08:00
"accessKey" : "some_access_key3" ,
"secretKey" : "some_secret_key3"
2020-02-10 06:30:02 +08:00
}
] ,
"actions" : [
"Read" ,
2020-10-09 01:11:59 +08:00
"List" ,
"Tagging" ,
2020-02-10 06:30:02 +08:00
"Write"
]
2020-02-23 13:34:18 +08:00
} ,
{
"name" : "user_limited_to_bucket1" ,
"credentials" : [
{
"accessKey" : "some_access_key4" ,
"secretKey" : "some_secret_key4"
}
] ,
"actions" : [
"Read:bucket1" ,
2020-10-09 01:11:59 +08:00
"List:bucket1" ,
"Tagging:bucket1" ,
2020-02-23 13:34:18 +08:00
"Write:bucket1"
]
2020-02-10 06:30:02 +08:00
}
]
}
2018-07-18 17:37:09 +08:00
` ,
}
func runS3 ( cmd * Command , args [ ] string ) bool {
2024-07-17 00:15:55 +08:00
util . LoadSecurityConfiguration ( )
2019-02-19 04:11:52 +08:00
2024-07-13 01:56:26 +08:00
switch {
case * s3StandaloneOptions . metricsHttpIp != "" :
// noting to do, use s3StandaloneOptions.metricsHttpIp
case * s3StandaloneOptions . bindIp != "" :
* s3StandaloneOptions . metricsHttpIp = * s3StandaloneOptions . bindIp
}
go stats_collect . StartMetricsServer ( * s3StandaloneOptions . metricsHttpIp , * s3StandaloneOptions . metricsHttpPort )
2020-09-25 01:21:23 +08:00
2019-04-24 15:18:01 +08:00
return s3StandaloneOptions . startS3Server ( )
}
2023-06-05 05:27:56 +08: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 15:18:01 +08:00
func ( s3opt * S3Options ) startS3Server ( ) bool {
2021-09-13 13:47:52 +08:00
filerAddress := pb . ServerAddress ( * s3opt . filer )
2018-07-18 17:37:09 +08:00
2020-02-25 06:34:14 +08:00
filerBucketsPath := "/buckets"
2023-05-17 00:39:43 +08:00
filerGroup := ""
2020-02-25 06:34:14 +08:00
grpcDialOption := security . LoadClientTLS ( util . GetViper ( ) , "grpc.client" )
2020-09-17 21:56:15 +08:00
// metrics read from the filer
var metricsAddress string
var metricsIntervalSec int
2020-02-27 08:49:47 +08:00
for {
2023-01-20 17:48:12 +08:00
err := pb . WithGrpcFilerClient ( false , 0 , filerAddress , grpcDialOption , func ( client filer_pb . SeaweedFilerClient ) error {
2020-02-27 08:49:47 +08:00
resp , err := client . GetFilerConfiguration ( context . Background ( ) , & filer_pb . GetFilerConfigurationRequest { } )
if err != nil {
2021-09-13 13:47:52 +08:00
return fmt . Errorf ( "get filer %s configuration: %v" , filerAddress , err )
2020-02-27 08:49:47 +08:00
}
filerBucketsPath = resp . DirBuckets
2023-05-17 00:39:43 +08:00
filerGroup = resp . FilerGroup
2020-09-17 21:56:15 +08:00
metricsAddress , metricsIntervalSec = resp . MetricsAddress , int ( resp . MetricsIntervalSec )
2020-02-27 08:49:47 +08:00
glog . V ( 0 ) . Infof ( "S3 read filer buckets dir: %s" , filerBucketsPath )
return nil
} )
2020-02-25 06:34:14 +08:00
if err != nil {
2021-09-13 13:47:52 +08:00
glog . V ( 0 ) . Infof ( "wait to connect to filer %s grpc address %s" , * s3opt . filer , filerAddress . ToGrpcAddress ( ) )
2020-02-27 08:49:47 +08:00
time . Sleep ( time . Second )
} else {
2021-09-13 13:47:52 +08:00
glog . V ( 0 ) . Infof ( "connected to filer %s grpc address %s" , * s3opt . filer , filerAddress . ToGrpcAddress ( ) )
2020-02-27 08:49:47 +08:00
break
2020-02-25 06:34:14 +08:00
}
}
2020-09-19 15:03:00 +08:00
2020-09-25 01:21:23 +08:00
go stats_collect . LoopPushingMetric ( "s3" , stats_collect . SourceName ( uint32 ( * s3opt . port ) ) , metricsAddress , metricsIntervalSec )
2020-02-25 06:34:14 +08:00
2018-07-18 17:37:09 +08:00
router := mux . NewRouter ( ) . SkipClean ( true )
2022-09-06 23:20:23 +08:00
var localFilerSocket string
if s3opt . localFilerSocket != nil {
localFilerSocket = * s3opt . localFilerSocket
}
2022-05-15 15:43:37 +08:00
s3ApiServer , s3ApiServer_err := s3api . NewS3ApiServer ( router , & s3api . S3ApiServerOption {
2022-03-31 01:46:13 +08:00
Filer : filerAddress ,
Port : * s3opt . port ,
Config : * s3opt . config ,
DomainName : * s3opt . domainName ,
2023-12-21 08:21:11 +08:00
AllowedOrigins : strings . Split ( * s3opt . allowedOrigins , "," ) ,
2022-03-31 01:46:13 +08:00
BucketsPath : filerBucketsPath ,
GrpcDialOption : grpcDialOption ,
AllowEmptyFolder : * s3opt . allowEmptyFolder ,
AllowDeleteBucketNotEmpty : * s3opt . allowDeleteBucketNotEmpty ,
2022-09-06 23:20:23 +08:00
LocalFilerSocket : localFilerSocket ,
2022-08-05 08:35:00 +08:00
DataCenter : * s3opt . dataCenter ,
2023-05-17 00:39:43 +08:00
FilerGroup : filerGroup ,
2018-07-18 17:37:09 +08:00
} )
if s3ApiServer_err != nil {
glog . Fatalf ( "S3 API Server startup error: %v" , s3ApiServer_err )
}
2020-09-24 20:48:39 +08:00
2018-07-23 12:28:54 +08:00
httpS := & http . Server { Handler : router }
2022-05-15 15:43:37 +08:00
if * s3opt . portGrpc == 0 {
* s3opt . portGrpc = 10000 + * s3opt . port
}
2022-03-12 06:02:39 +08:00
if * s3opt . bindIp == "" {
* s3opt . bindIp = "localhost"
}
2023-06-27 07:22:45 +08: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-18 03:34:37 +08:00
listenAddress := fmt . Sprintf ( "%s:%d" , * s3opt . bindIp , * s3opt . port )
2022-09-15 02:59:55 +08:00
s3ApiListener , s3ApiLocalListener , err := util . NewIpAndLocalListeners ( * s3opt . bindIp , * s3opt . port , time . Duration ( 10 ) * time . Second )
2018-07-23 12:28:54 +08:00
if err != nil {
glog . Fatalf ( "S3 API Server listener on %s error: %v" , listenAddress , err )
2018-07-18 17:37:09 +08:00
}
2021-12-07 21:20:52 +08:00
if len ( * s3opt . auditLogConfig ) > 0 {
s3err . InitAuditLog ( * s3opt . auditLogConfig )
2021-12-10 22:40:32 +08:00
if s3err . Logger != nil {
defer s3err . Logger . Close ( )
}
2021-12-07 21:20:52 +08:00
}
2022-05-15 15:43:37 +08: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 15:18:01 +08:00
if * s3opt . tlsPrivateKey != "" {
2023-06-05 05:27:56 +08: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-14 00:02:24 +08: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 ,
}
2024-08-02 11:00:22 +08:00
err = security . FixTlsConfig ( util . GetViper ( ) , httpS . TLSConfig )
if err != nil {
glog . Fatalf ( "error with tls config: %v" , err )
}
2023-06-14 14:58:49 +08: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 13:28:18 +08:00
go func ( ) {
2023-06-14 14:58:49 +08:00
if err = httpS . ServeTLS ( s3ApiListenerHttps , "" , "" ) ; err != nil {
2022-03-16 13:28:18 +08:00
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
} ( )
}
2023-06-14 14:58:49 +08:00
}
if * s3opt . tlsPrivateKey == "" || * s3opt . portHttps > 0 {
2020-06-02 15:10:35 +08:00
glog . V ( 0 ) . Infof ( "Start Seaweed S3 API Server %s at http port %d" , util . Version ( ) , * s3opt . port )
2022-09-15 02:59:55 +08:00
if s3ApiLocalListener != nil {
2022-03-16 13:28:18 +08:00
go func ( ) {
2022-09-15 02:59:55 +08:00
if err = httpS . Serve ( s3ApiLocalListener ) ; err != nil {
2022-03-16 13:28:18 +08:00
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
} ( )
}
2018-07-23 12:28:54 +08:00
if err = httpS . Serve ( s3ApiListener ) ; err != nil {
glog . Fatalf ( "S3 API Server Fail to serve: %v" , err )
}
2018-07-18 17:37:09 +08:00
}
return true
}