2020-04-02 12:51:51 -07:00
|
|
|
// This file is part of MinIO Console Server
|
2021-01-19 17:04:13 -06:00
|
|
|
// Copyright (c) 2021 MinIO, Inc.
|
2020-04-01 18:18:57 -07:00
|
|
|
//
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
// This file is safe to edit. Once it exists it will not be overwritten
|
|
|
|
|
|
|
|
|
|
package restapi
|
|
|
|
|
|
|
|
|
|
import (
|
2020-10-21 13:13:40 -07:00
|
|
|
"bytes"
|
2020-04-01 18:18:57 -07:00
|
|
|
"crypto/tls"
|
2020-11-13 16:26:03 -08:00
|
|
|
"fmt"
|
2021-03-05 10:39:17 -08:00
|
|
|
"io"
|
|
|
|
|
"io/fs"
|
2020-04-01 18:18:57 -07:00
|
|
|
"log"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strings"
|
2020-10-21 13:13:40 -07:00
|
|
|
"time"
|
2020-04-01 18:18:57 -07:00
|
|
|
|
2021-03-05 10:39:17 -08:00
|
|
|
portal_ui "github.com/minio/console/portal-ui"
|
2020-04-01 18:18:57 -07:00
|
|
|
|
|
|
|
|
"github.com/go-openapi/errors"
|
|
|
|
|
"github.com/go-openapi/runtime"
|
2020-10-29 22:26:48 -07:00
|
|
|
"github.com/go-openapi/swag"
|
|
|
|
|
"github.com/minio/console/models"
|
|
|
|
|
"github.com/minio/console/pkg/auth"
|
2020-07-26 00:34:17 -07:00
|
|
|
"github.com/minio/console/restapi/operations"
|
2020-04-06 13:24:15 -07:00
|
|
|
"github.com/unrolled/secure"
|
2020-04-01 18:18:57 -07:00
|
|
|
)
|
|
|
|
|
|
2020-07-26 00:34:17 -07:00
|
|
|
//go:generate swagger generate server --target ../../console --name Console --spec ../swagger.yml
|
2020-04-01 18:18:57 -07:00
|
|
|
|
2020-10-29 22:26:48 -07:00
|
|
|
var additionalServerFlags = struct {
|
|
|
|
|
CertsDir string `long:"certs-dir" description:"path to certs directory" env:"CONSOLE_CERTS_DIR"`
|
|
|
|
|
}{}
|
|
|
|
|
|
2020-07-26 00:34:17 -07:00
|
|
|
func configureFlags(api *operations.ConsoleAPI) {
|
2020-10-29 22:26:48 -07:00
|
|
|
api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{
|
|
|
|
|
{
|
|
|
|
|
ShortDescription: "additional server flags",
|
|
|
|
|
Options: &additionalServerFlags,
|
|
|
|
|
},
|
|
|
|
|
}
|
2020-04-01 18:18:57 -07:00
|
|
|
}
|
|
|
|
|
|
2020-07-26 00:34:17 -07:00
|
|
|
func configureAPI(api *operations.ConsoleAPI) http.Handler {
|
2020-04-01 18:18:57 -07:00
|
|
|
// configure the api here
|
|
|
|
|
api.ServeError = errors.ServeError
|
|
|
|
|
|
|
|
|
|
// Set your custom logger if needed. Default one is log.Printf
|
|
|
|
|
// Expected interface func(string, ...interface{})
|
|
|
|
|
//
|
|
|
|
|
// Example:
|
|
|
|
|
// api.Logger = log.Printf
|
|
|
|
|
|
|
|
|
|
api.JSONConsumer = runtime.JSONConsumer()
|
|
|
|
|
|
|
|
|
|
api.JSONProducer = runtime.JSONProducer()
|
|
|
|
|
// Applies when the "x-token" header is set
|
|
|
|
|
|
2020-04-02 12:06:52 -07:00
|
|
|
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
|
2020-10-19 15:32:21 -07:00
|
|
|
// we are validating the session token by decrypting the claims inside, if the operation succeed that means the jwt
|
2020-07-10 19:14:28 -07:00
|
|
|
// was generated and signed by us in the first place
|
TLS with user provided certificates and KES support for MinIO (#213)
This PR adds the following features:
- Allow user to provide its own keypair certificates for enable TLS in
MinIO
- Allow user to configure data encryption at rest in MinIO with KES
- Removes JWT schema for login and instead Console authentication will use
encrypted session tokens
Enable TLS between client and MinIO with user provided certificates
Instead of using AutoCert feature now the user can provide `cert` and
`key` via `tls` object, values must be valid `x509.Certificate`
formatted files encoded in `base64`
Enable encryption at rest configuring KES
User can deploy KES via Console/Operator by defining the encryption
object, AutoCert must be enabled or custom certificates for KES must be
provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`,
previous configuration of the KMS is necessary.
eg of body request for create-tenant
```
{
"name": "honeywell",
"access_key": "minio",
"secret_key": "minio123",
"enable_mcs": false,
"enable_ssl": false,
"service_name": "honeywell",
"zones": [
{
"name": "honeywell-zone-1",
"servers": 1,
"volumes_per_server": 4,
"volume_configuration": {
"size": 256000000,
"storage_class": "vsan-default-storage-policy"
}
}
],
"namespace": "default",
"tls": {
"tls.crt": "",
"tls.key": ""
},
"encryption": {
"server": {
"tls.crt": "",
"tls.key": ""
},
"client": {
"tls.crt": "",
"tls.key": ""
},
"vault": {
"endpoint": "http://vault:8200",
"prefix": "",
"approle": {
"id": "",
"secret": ""
}
}
}
}
```
2020-07-30 17:49:56 -07:00
|
|
|
claims, err := auth.SessionTokenAuthenticate(token)
|
2020-07-10 19:14:28 -07:00
|
|
|
if err != nil {
|
|
|
|
|
log.Println(err)
|
|
|
|
|
return nil, errors.New(401, "incorrect api key auth")
|
2020-04-01 18:18:57 -07:00
|
|
|
}
|
2020-07-10 19:14:28 -07:00
|
|
|
return &models.Principal{
|
2020-12-07 17:11:08 -06:00
|
|
|
STSAccessKeyID: claims.STSAccessKeyID,
|
|
|
|
|
Actions: claims.Actions,
|
|
|
|
|
STSSecretAccessKey: claims.STSSecretAccessKey,
|
|
|
|
|
STSSessionToken: claims.STSSessionToken,
|
|
|
|
|
AccountAccessKey: claims.AccountAccessKey,
|
2020-07-10 19:14:28 -07:00
|
|
|
}, nil
|
2020-04-01 18:18:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Register login handlers
|
|
|
|
|
registerLoginHandlers(api)
|
2020-04-08 12:36:14 -07:00
|
|
|
// Register logout handlers
|
|
|
|
|
registerLogoutHandlers(api)
|
2020-04-01 18:18:57 -07:00
|
|
|
// Register bucket handlers
|
|
|
|
|
registerBucketsHandlers(api)
|
|
|
|
|
// Register all users handlers
|
|
|
|
|
registerUsersHandlers(api)
|
|
|
|
|
// Register groups handlers
|
|
|
|
|
registerGroupsHandlers(api)
|
|
|
|
|
// Register policies handlers
|
|
|
|
|
registersPoliciesHandler(api)
|
|
|
|
|
// Register configurations handlers
|
|
|
|
|
registerConfigHandlers(api)
|
|
|
|
|
// Register bucket events handlers
|
|
|
|
|
registerBucketEventsHandlers(api)
|
|
|
|
|
// Register service handlers
|
|
|
|
|
registerServiceHandlers(api)
|
2020-04-02 09:57:59 -07:00
|
|
|
// Register profiling handlers
|
|
|
|
|
registerProfilingHandler(api)
|
2020-04-02 15:54:34 -07:00
|
|
|
// Register session handlers
|
|
|
|
|
registerSessionHandlers(api)
|
2020-04-02 20:15:39 -07:00
|
|
|
// Register admin info handlers
|
|
|
|
|
registerAdminInfoHandlers(api)
|
2020-04-03 14:27:47 -07:00
|
|
|
// Register admin arns handlers
|
|
|
|
|
registerAdminArnsHandlers(api)
|
2020-04-09 16:07:26 -07:00
|
|
|
// Register admin notification endpoints handlers
|
|
|
|
|
registerAdminNotificationEndpointsHandlers(api)
|
2020-04-29 18:28:28 -07:00
|
|
|
// Register admin Service Account Handlers
|
|
|
|
|
registerServiceAccountsHandlers(api)
|
2020-09-28 12:46:08 -05:00
|
|
|
// Register admin remote buckets
|
|
|
|
|
registerAdminBucketRemoteHandlers(api)
|
2021-01-13 14:08:32 -06:00
|
|
|
// Register admin log search
|
|
|
|
|
registerLogSearchHandlers(api)
|
2021-01-12 15:55:07 -06:00
|
|
|
// Register admin subscription handlers
|
|
|
|
|
registerSubscriptionHandlers(api)
|
2020-04-01 18:18:57 -07:00
|
|
|
|
2020-07-08 13:55:08 -07:00
|
|
|
// Operator Console
|
2020-07-01 18:03:22 -07:00
|
|
|
// Register tenant handlers
|
|
|
|
|
registerTenantHandlers(api)
|
|
|
|
|
// Register ResourceQuota handlers
|
|
|
|
|
registerResourceQuotaHandlers(api)
|
2020-08-28 21:06:45 -07:00
|
|
|
// Register Nodes' handlers
|
|
|
|
|
registerNodesHandlers(api)
|
2020-09-22 11:15:21 -07:00
|
|
|
// Register Parity' handlers
|
|
|
|
|
registerParityHandlers(api)
|
2020-09-29 14:34:51 -07:00
|
|
|
// Register Object's Handlers
|
|
|
|
|
registerObjectsHandlers(api)
|
2020-10-01 18:59:20 -07:00
|
|
|
// Register Bucket Quota's Handlers
|
|
|
|
|
registerBucketQuotaHandlers(api)
|
2020-12-07 17:11:08 -06:00
|
|
|
// Register Account handlers
|
|
|
|
|
registerAccountHandlers(api)
|
2021-03-18 13:10:05 -06:00
|
|
|
// Direct CSI handlers
|
|
|
|
|
registerDirectCSIHandlers(api)
|
2020-07-01 18:03:22 -07:00
|
|
|
|
2020-04-01 18:18:57 -07:00
|
|
|
api.PreServerShutdown = func() {}
|
|
|
|
|
|
|
|
|
|
api.ServerShutdown = func() {}
|
|
|
|
|
|
|
|
|
|
return setupGlobalMiddleware(api.Serve(setupMiddlewares))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The TLS configuration before HTTPS server starts.
|
|
|
|
|
func configureTLS(tlsConfig *tls.Config) {
|
2020-11-20 11:52:34 -08:00
|
|
|
if GlobalRootCAs != nil {
|
|
|
|
|
// Add the global public crts as part of global root CAs
|
|
|
|
|
for _, publicCrt := range GlobalPublicCerts {
|
|
|
|
|
GlobalRootCAs.AddCert(publicCrt)
|
|
|
|
|
}
|
|
|
|
|
tlsConfig.RootCAs = GlobalRootCAs
|
2020-10-29 22:26:48 -07:00
|
|
|
}
|
|
|
|
|
if GlobalTLSCertsManager != nil {
|
|
|
|
|
tlsConfig.GetCertificate = GlobalTLSCertsManager.GetCertificate
|
|
|
|
|
}
|
2020-04-01 18:18:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// As soon as server is initialized but not run yet, this function will be called.
|
|
|
|
|
// If you need to modify a config, store server instance to stop it individually later, this is the place.
|
|
|
|
|
// This function can be called multiple times, depending on the number of serving schemes.
|
|
|
|
|
// scheme value will be set accordingly: "http", "https" or "unix"
|
|
|
|
|
func configureServer(s *http.Server, scheme, addr string) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
|
|
|
|
|
// The middleware executes after routing but before authentication, binding and validation
|
|
|
|
|
func setupMiddlewares(handler http.Handler) http.Handler {
|
|
|
|
|
return handler
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
|
|
|
|
|
// So this is a good place to plug in a panic handling middleware, logging and metrics
|
|
|
|
|
func setupGlobalMiddleware(handler http.Handler) http.Handler {
|
2020-11-13 16:26:03 -08:00
|
|
|
// handle cookie or authorization header for session
|
|
|
|
|
next := AuthenticationMiddleware(handler)
|
2020-04-01 18:18:57 -07:00
|
|
|
// serve static files
|
2020-11-13 16:26:03 -08:00
|
|
|
next = FileServerMiddleware(next)
|
2020-04-06 13:24:15 -07:00
|
|
|
// Secure middleware, this middleware wrap all the previous handlers and add
|
|
|
|
|
// HTTP security headers
|
|
|
|
|
secureOptions := secure.Options{
|
2020-04-06 16:04:18 -07:00
|
|
|
AllowedHosts: getSecureAllowedHosts(),
|
|
|
|
|
AllowedHostsAreRegex: getSecureAllowedHostsAreRegex(),
|
|
|
|
|
HostsProxyHeaders: getSecureHostsProxyHeaders(),
|
2021-01-26 22:08:21 -06:00
|
|
|
SSLRedirect: GetTLSRedirect() == "on" && len(GlobalPublicCerts) > 0,
|
2020-08-09 16:08:58 -07:00
|
|
|
SSLHost: getSecureTLSHost(),
|
2020-04-06 16:04:18 -07:00
|
|
|
STSSeconds: getSecureSTSSeconds(),
|
|
|
|
|
STSIncludeSubdomains: getSecureSTSIncludeSubdomains(),
|
|
|
|
|
STSPreload: getSecureSTSPreload(),
|
2020-08-09 16:08:58 -07:00
|
|
|
SSLTemporaryRedirect: getSecureTLSTemporaryRedirect(),
|
2020-04-06 16:04:18 -07:00
|
|
|
SSLHostFunc: nil,
|
|
|
|
|
ForceSTSHeader: getSecureForceSTSHeader(),
|
|
|
|
|
FrameDeny: getSecureFrameDeny(),
|
|
|
|
|
ContentTypeNosniff: getSecureContentTypeNonSniff(),
|
|
|
|
|
BrowserXssFilter: getSecureBrowserXSSFilter(),
|
|
|
|
|
ContentSecurityPolicy: getSecureContentSecurityPolicy(),
|
2020-04-06 13:24:15 -07:00
|
|
|
ContentSecurityPolicyReportOnly: getSecureContentSecurityPolicyReportOnly(),
|
2020-04-06 16:04:18 -07:00
|
|
|
PublicKey: getSecurePublicKey(),
|
|
|
|
|
ReferrerPolicy: getSecureReferrerPolicy(),
|
|
|
|
|
FeaturePolicy: getSecureFeaturePolicy(),
|
|
|
|
|
ExpectCTHeader: getSecureExpectCTHeader(),
|
|
|
|
|
IsDevelopment: !getProductionMode(),
|
2020-04-06 13:24:15 -07:00
|
|
|
}
|
|
|
|
|
secureMiddleware := secure.New(secureOptions)
|
|
|
|
|
app := secureMiddleware.Handler(next)
|
|
|
|
|
return app
|
2020-04-01 18:18:57 -07:00
|
|
|
}
|
|
|
|
|
|
2020-11-13 16:26:03 -08:00
|
|
|
func AuthenticationMiddleware(next http.Handler) http.Handler {
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
// prioritize authorization header and skip
|
|
|
|
|
if r.Header.Get("Authorization") != "" {
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
tokenCookie, err := r.Cookie("token")
|
|
|
|
|
if err != nil {
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
currentTime := time.Now()
|
|
|
|
|
if tokenCookie.Expires.After(currentTime) {
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
token := tokenCookie.Value
|
|
|
|
|
if token != "" {
|
|
|
|
|
r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
|
|
|
}
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-01 18:18:57 -07:00
|
|
|
// FileServerMiddleware serves files from the static folder
|
|
|
|
|
func FileServerMiddleware(next http.Handler) http.Handler {
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2021-02-16 12:53:18 -08:00
|
|
|
w.Header().Set("Server", globalAppName) // do not add version information
|
2020-04-30 10:50:51 -07:00
|
|
|
switch {
|
|
|
|
|
case strings.HasPrefix(r.URL.Path, "/ws"):
|
|
|
|
|
serveWS(w, r)
|
|
|
|
|
case strings.HasPrefix(r.URL.Path, "/api"):
|
2020-04-01 18:18:57 -07:00
|
|
|
next.ServeHTTP(w, r)
|
2020-04-30 10:50:51 -07:00
|
|
|
default:
|
2021-03-05 10:39:17 -08:00
|
|
|
buildFs, err := fs.Sub(portal_ui.GetStaticAssets(), "build")
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
wrapHandlerSinglePageApplication(http.FileServer(http.FS(buildFs))).ServeHTTP(w, r)
|
2020-05-08 16:36:08 -07:00
|
|
|
|
2020-04-01 18:18:57 -07:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2020-05-08 16:36:08 -07:00
|
|
|
|
|
|
|
|
type notFoundRedirectRespWr struct {
|
|
|
|
|
http.ResponseWriter // We embed http.ResponseWriter
|
|
|
|
|
status int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *notFoundRedirectRespWr) WriteHeader(status int) {
|
|
|
|
|
w.status = status // Store the status for our own use
|
|
|
|
|
if status != http.StatusNotFound {
|
|
|
|
|
w.ResponseWriter.WriteHeader(status)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *notFoundRedirectRespWr) Write(p []byte) (int, error) {
|
|
|
|
|
if w.status != http.StatusNotFound {
|
|
|
|
|
return w.ResponseWriter.Write(p)
|
|
|
|
|
}
|
|
|
|
|
return len(p), nil // Lie that we successfully wrote it
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wrapHandlerSinglePageApplication handles a http.FileServer returning a 404 and overrides it with index.html
|
|
|
|
|
func wrapHandlerSinglePageApplication(h http.Handler) http.HandlerFunc {
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
nfrw := ¬FoundRedirectRespWr{ResponseWriter: w}
|
|
|
|
|
h.ServeHTTP(nfrw, r)
|
|
|
|
|
if nfrw.status == 404 {
|
2021-03-05 10:39:17 -08:00
|
|
|
indexPage, _ := portal_ui.GetStaticAssets().Open("build/index.html")
|
|
|
|
|
indexPageBytes, _ := io.ReadAll(indexPage)
|
2020-10-21 13:13:40 -07:00
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
2021-03-05 10:39:17 -08:00
|
|
|
http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader(indexPageBytes))
|
2020-05-08 16:36:08 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|