s3.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. package command
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "fmt"
  6. "net"
  7. "net/http"
  8. "os"
  9. "runtime"
  10. "time"
  11. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  12. "google.golang.org/grpc/credentials/tls/certprovider"
  13. "google.golang.org/grpc/credentials/tls/certprovider/pemfile"
  14. "google.golang.org/grpc/reflection"
  15. "github.com/seaweedfs/seaweedfs/weed/pb"
  16. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  17. "github.com/seaweedfs/seaweedfs/weed/pb/s3_pb"
  18. "github.com/seaweedfs/seaweedfs/weed/security"
  19. "github.com/gorilla/mux"
  20. "github.com/seaweedfs/seaweedfs/weed/glog"
  21. "github.com/seaweedfs/seaweedfs/weed/s3api"
  22. stats_collect "github.com/seaweedfs/seaweedfs/weed/stats"
  23. "github.com/seaweedfs/seaweedfs/weed/util"
  24. )
  25. var (
  26. s3StandaloneOptions S3Options
  27. )
  28. type S3Options struct {
  29. filer *string
  30. bindIp *string
  31. port *int
  32. portHttps *int
  33. portGrpc *int
  34. config *string
  35. domainName *string
  36. tlsPrivateKey *string
  37. tlsCertificate *string
  38. metricsHttpPort *int
  39. allowEmptyFolder *bool
  40. allowDeleteBucketNotEmpty *bool
  41. auditLogConfig *string
  42. localFilerSocket *string
  43. dataCenter *string
  44. localSocket *string
  45. certProvider certprovider.Provider
  46. }
  47. func init() {
  48. cmdS3.Run = runS3 // break init cycle
  49. s3StandaloneOptions.filer = cmdS3.Flag.String("filer", "localhost:8888", "filer server address")
  50. s3StandaloneOptions.bindIp = cmdS3.Flag.String("ip.bind", "", "ip address to bind to. Default to localhost.")
  51. s3StandaloneOptions.port = cmdS3.Flag.Int("port", 8333, "s3 server http listen port")
  52. s3StandaloneOptions.portHttps = cmdS3.Flag.Int("port.https", 0, "s3 server https listen port")
  53. s3StandaloneOptions.portGrpc = cmdS3.Flag.Int("port.grpc", 0, "s3 server grpc listen port")
  54. s3StandaloneOptions.domainName = cmdS3.Flag.String("domainName", "", "suffix of the host name in comma separated list, {bucket}.{domainName}")
  55. s3StandaloneOptions.dataCenter = cmdS3.Flag.String("dataCenter", "", "prefer to read and write to volumes in this data center")
  56. s3StandaloneOptions.config = cmdS3.Flag.String("config", "", "path to the config file")
  57. s3StandaloneOptions.auditLogConfig = cmdS3.Flag.String("auditLogConfig", "", "path to the audit log config file")
  58. s3StandaloneOptions.tlsPrivateKey = cmdS3.Flag.String("key.file", "", "path to the TLS private key file")
  59. s3StandaloneOptions.tlsCertificate = cmdS3.Flag.String("cert.file", "", "path to the TLS certificate file")
  60. s3StandaloneOptions.metricsHttpPort = cmdS3.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
  61. s3StandaloneOptions.allowEmptyFolder = cmdS3.Flag.Bool("allowEmptyFolder", true, "allow empty folders")
  62. s3StandaloneOptions.allowDeleteBucketNotEmpty = cmdS3.Flag.Bool("allowDeleteBucketNotEmpty", true, "allow recursive deleting all entries along with bucket")
  63. s3StandaloneOptions.localFilerSocket = cmdS3.Flag.String("localFilerSocket", "", "local filer socket path")
  64. s3StandaloneOptions.localSocket = cmdS3.Flag.String("localSocket", "", "default to /tmp/seaweedfs-s3-<port>.sock")
  65. }
  66. var cmdS3 = &Command{
  67. UsageLine: "s3 [-port=8333] [-filer=<ip:port>] [-config=</path/to/config.json>]",
  68. Short: "start a s3 API compatible server that is backed by a filer",
  69. Long: `start a s3 API compatible server that is backed by a filer.
  70. By default, you can use any access key and secret key to access the S3 APIs.
  71. To enable credential based access, create a config.json file similar to this:
  72. {
  73. "identities": [
  74. {
  75. "name": "anonymous",
  76. "actions": [
  77. "Read"
  78. ]
  79. },
  80. {
  81. "name": "some_admin_user",
  82. "credentials": [
  83. {
  84. "accessKey": "some_access_key1",
  85. "secretKey": "some_secret_key1"
  86. }
  87. ],
  88. "actions": [
  89. "Admin",
  90. "Read",
  91. "List",
  92. "Tagging",
  93. "Write"
  94. ]
  95. },
  96. {
  97. "name": "some_read_only_user",
  98. "credentials": [
  99. {
  100. "accessKey": "some_access_key2",
  101. "secretKey": "some_secret_key2"
  102. }
  103. ],
  104. "actions": [
  105. "Read"
  106. ]
  107. },
  108. {
  109. "name": "some_normal_user",
  110. "credentials": [
  111. {
  112. "accessKey": "some_access_key3",
  113. "secretKey": "some_secret_key3"
  114. }
  115. ],
  116. "actions": [
  117. "Read",
  118. "List",
  119. "Tagging",
  120. "Write"
  121. ]
  122. },
  123. {
  124. "name": "user_limited_to_bucket1",
  125. "credentials": [
  126. {
  127. "accessKey": "some_access_key4",
  128. "secretKey": "some_secret_key4"
  129. }
  130. ],
  131. "actions": [
  132. "Read:bucket1",
  133. "List:bucket1",
  134. "Tagging:bucket1",
  135. "Write:bucket1"
  136. ]
  137. }
  138. ]
  139. }
  140. `,
  141. }
  142. func runS3(cmd *Command, args []string) bool {
  143. util.LoadConfiguration("security", false)
  144. go stats_collect.StartMetricsServer(*s3StandaloneOptions.bindIp, *s3StandaloneOptions.metricsHttpPort)
  145. return s3StandaloneOptions.startS3Server()
  146. }
  147. // GetCertificateWithUpdate Auto refreshing TSL certificate
  148. func (S3opt *S3Options) GetCertificateWithUpdate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
  149. certs, err := S3opt.certProvider.KeyMaterial(context.Background())
  150. return &certs.Certs[0], err
  151. }
  152. func (s3opt *S3Options) startS3Server() bool {
  153. filerAddress := pb.ServerAddress(*s3opt.filer)
  154. filerBucketsPath := "/buckets"
  155. filerGroup := ""
  156. grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
  157. // metrics read from the filer
  158. var metricsAddress string
  159. var metricsIntervalSec int
  160. for {
  161. err := pb.WithGrpcFilerClient(false, 0, filerAddress, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  162. resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
  163. if err != nil {
  164. return fmt.Errorf("get filer %s configuration: %v", filerAddress, err)
  165. }
  166. filerBucketsPath = resp.DirBuckets
  167. filerGroup = resp.FilerGroup
  168. metricsAddress, metricsIntervalSec = resp.MetricsAddress, int(resp.MetricsIntervalSec)
  169. glog.V(0).Infof("S3 read filer buckets dir: %s", filerBucketsPath)
  170. return nil
  171. })
  172. if err != nil {
  173. glog.V(0).Infof("wait to connect to filer %s grpc address %s", *s3opt.filer, filerAddress.ToGrpcAddress())
  174. time.Sleep(time.Second)
  175. } else {
  176. glog.V(0).Infof("connected to filer %s grpc address %s", *s3opt.filer, filerAddress.ToGrpcAddress())
  177. break
  178. }
  179. }
  180. go stats_collect.LoopPushingMetric("s3", stats_collect.SourceName(uint32(*s3opt.port)), metricsAddress, metricsIntervalSec)
  181. router := mux.NewRouter().SkipClean(true)
  182. var localFilerSocket string
  183. if s3opt.localFilerSocket != nil {
  184. localFilerSocket = *s3opt.localFilerSocket
  185. }
  186. s3ApiServer, s3ApiServer_err := s3api.NewS3ApiServer(router, &s3api.S3ApiServerOption{
  187. Filer: filerAddress,
  188. Port: *s3opt.port,
  189. Config: *s3opt.config,
  190. DomainName: *s3opt.domainName,
  191. BucketsPath: filerBucketsPath,
  192. GrpcDialOption: grpcDialOption,
  193. AllowEmptyFolder: *s3opt.allowEmptyFolder,
  194. AllowDeleteBucketNotEmpty: *s3opt.allowDeleteBucketNotEmpty,
  195. LocalFilerSocket: localFilerSocket,
  196. DataCenter: *s3opt.dataCenter,
  197. FilerGroup: filerGroup,
  198. })
  199. if s3ApiServer_err != nil {
  200. glog.Fatalf("S3 API Server startup error: %v", s3ApiServer_err)
  201. }
  202. httpS := &http.Server{Handler: router}
  203. if *s3opt.portGrpc == 0 {
  204. *s3opt.portGrpc = 10000 + *s3opt.port
  205. }
  206. if *s3opt.bindIp == "" {
  207. *s3opt.bindIp = "localhost"
  208. }
  209. if runtime.GOOS != "windows" {
  210. localSocket := *s3opt.localSocket
  211. if localSocket == "" {
  212. localSocket = fmt.Sprintf("/tmp/seaweedfs-s3-%d.sock", *s3opt.port)
  213. }
  214. if err := os.Remove(localSocket); err != nil && !os.IsNotExist(err) {
  215. glog.Fatalf("Failed to remove %s, error: %s", localSocket, err.Error())
  216. }
  217. go func() {
  218. // start on local unix socket
  219. s3SocketListener, err := net.Listen("unix", localSocket)
  220. if err != nil {
  221. glog.Fatalf("Failed to listen on %s: %v", localSocket, err)
  222. }
  223. httpS.Serve(s3SocketListener)
  224. }()
  225. }
  226. listenAddress := fmt.Sprintf("%s:%d", *s3opt.bindIp, *s3opt.port)
  227. s3ApiListener, s3ApiLocalListener, err := util.NewIpAndLocalListeners(*s3opt.bindIp, *s3opt.port, time.Duration(10)*time.Second)
  228. if err != nil {
  229. glog.Fatalf("S3 API Server listener on %s error: %v", listenAddress, err)
  230. }
  231. if len(*s3opt.auditLogConfig) > 0 {
  232. s3err.InitAuditLog(*s3opt.auditLogConfig)
  233. if s3err.Logger != nil {
  234. defer s3err.Logger.Close()
  235. }
  236. }
  237. // starting grpc server
  238. grpcPort := *s3opt.portGrpc
  239. grpcL, grpcLocalL, err := util.NewIpAndLocalListeners(*s3opt.bindIp, grpcPort, 0)
  240. if err != nil {
  241. glog.Fatalf("s3 failed to listen on grpc port %d: %v", grpcPort, err)
  242. }
  243. grpcS := pb.NewGrpcServer(security.LoadServerTLS(util.GetViper(), "grpc.s3"))
  244. s3_pb.RegisterSeaweedS3Server(grpcS, s3ApiServer)
  245. reflection.Register(grpcS)
  246. if grpcLocalL != nil {
  247. go grpcS.Serve(grpcLocalL)
  248. }
  249. go grpcS.Serve(grpcL)
  250. if *s3opt.tlsPrivateKey != "" {
  251. pemfileOptions := pemfile.Options{
  252. CertFile: *s3opt.tlsCertificate,
  253. KeyFile: *s3opt.tlsPrivateKey,
  254. RefreshDuration: security.CredRefreshingInterval,
  255. }
  256. if s3opt.certProvider, err = pemfile.NewProvider(pemfileOptions); err != nil {
  257. glog.Fatalf("pemfile.NewProvider(%v) failed: %v", pemfileOptions, err)
  258. }
  259. httpS.TLSConfig = &tls.Config{GetCertificate: s3opt.GetCertificateWithUpdate}
  260. if *s3opt.portHttps == 0 {
  261. glog.V(0).Infof("Start Seaweed S3 API Server %s at https port %d", util.Version(), *s3opt.port)
  262. if s3ApiLocalListener != nil {
  263. go func() {
  264. if err = httpS.ServeTLS(s3ApiLocalListener, "", ""); err != nil {
  265. glog.Fatalf("S3 API Server Fail to serve: %v", err)
  266. }
  267. }()
  268. }
  269. if err = httpS.ServeTLS(s3ApiListener, "", ""); err != nil {
  270. glog.Fatalf("S3 API Server Fail to serve: %v", err)
  271. }
  272. } else {
  273. glog.V(0).Infof("Start Seaweed S3 API Server %s at https port %d", util.Version(), *s3opt.portHttps)
  274. s3ApiListenerHttps, s3ApiLocalListenerHttps, _ := util.NewIpAndLocalListeners(
  275. *s3opt.bindIp, *s3opt.portHttps, time.Duration(10)*time.Second)
  276. if s3ApiLocalListenerHttps != nil {
  277. go func() {
  278. if err = httpS.ServeTLS(s3ApiLocalListenerHttps, "", ""); err != nil {
  279. glog.Fatalf("S3 API Server Fail to serve: %v", err)
  280. }
  281. }()
  282. }
  283. go func() {
  284. if err = httpS.ServeTLS(s3ApiListenerHttps, "", ""); err != nil {
  285. glog.Fatalf("S3 API Server Fail to serve: %v", err)
  286. }
  287. }()
  288. }
  289. }
  290. if *s3opt.tlsPrivateKey == "" || *s3opt.portHttps > 0 {
  291. glog.V(0).Infof("Start Seaweed S3 API Server %s at http port %d", util.Version(), *s3opt.port)
  292. if s3ApiLocalListener != nil {
  293. go func() {
  294. if err = httpS.Serve(s3ApiLocalListener); err != nil {
  295. glog.Fatalf("S3 API Server Fail to serve: %v", err)
  296. }
  297. }()
  298. }
  299. if err = httpS.Serve(s3ApiListener); err != nil {
  300. glog.Fatalf("S3 API Server Fail to serve: %v", err)
  301. }
  302. }
  303. return true
  304. }