diff --git a/Dockerfile.dev b/Dockerfile.dev index 8d88cce2..1c63817a 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM golang:1.21-alpine as build +FROM golang:1.22-alpine as build LABEL maintainer="MinIO Inc " diff --git a/cmd/kes/migrate.go b/cmd/kes/migrate.go index 09b08be7..e52f95b9 100644 --- a/cmd/kes/migrate.go +++ b/cmd/kes/migrate.go @@ -109,7 +109,7 @@ func migrate(args []string) { srcConf, err := kesconf.ReadFile(fromPath) cli.Assert(err == nil, err) - src, err := srcConf.KeyStore.Connect(ctx, false) + src, err := srcConf.KeyStore.Connect(ctx) cli.Assert(err == nil, err) iter := &kes.ListIter[string]{ @@ -121,7 +121,7 @@ func migrate(args []string) { dstConf, err := kesconf.ReadFile(toPath) cli.Assert(err == nil, err) - dst, err := dstConf.KeyStore.Connect(ctx, false) + dst, err := dstConf.KeyStore.Connect(ctx) cli.Assert(err == nil, err) var ( diff --git a/cmd/kes/server.go b/cmd/kes/server.go index 6a4da861..765742a6 100644 --- a/cmd/kes/server.go +++ b/cmd/kes/server.go @@ -87,7 +87,7 @@ func serverCmd(args []string) { cmd.StringVar(&tlsCertFlag, "cert", "", "Path to the TLS certificate") cmd.StringVar(&mtlsAuthFlag, "auth", "", "Controls how the server handles mTLS authentication") cmd.BoolVar(&devFlag, "dev", false, "Start the KES server in development mode") - cmd.BoolVar(&verboseFlag, "verbose", false, "Log verbose output (Vault only)") + cmd.BoolVar(&verboseFlag, "verbose", false, "Log verbose output") if err := cmd.Parse(args[1:]); err != nil { if errors.Is(err, flag.ErrHelp) { os.Exit(2) @@ -176,18 +176,26 @@ func startServer(addrFlag, configFlag string, verbose bool) error { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() - conf, err := rawConfig.Config(ctx, verbose) + srv := &kes.Server{} + logLevel := slog.LevelInfo + if rawConfig.Log != nil { + srv.ErrLevel.Set(rawConfig.Log.ErrLevel) + srv.AuditLevel.Set(rawConfig.Log.AuditLevel) + logLevel = rawConfig.Log.LogLevel + } + if verbose { + logLevel = slog.LevelDebug + } + slog.SetLogLoggerLevel(logLevel) + + conf, err := rawConfig.Config(ctx) if err != nil { return err } defer conf.Keys.Close() - srv := &kes.Server{} conf.Cache = configureCache(conf.Cache) - if rawConfig.Log != nil { - srv.ErrLevel.Set(rawConfig.Log.ErrLevel) - srv.AuditLevel.Set(rawConfig.Log.AuditLevel) - } + sighup := make(chan os.Signal, 10) signal.Notify(sighup, syscall.SIGHUP) defer signal.Stop(sighup) @@ -240,7 +248,7 @@ func startServer(addrFlag, configFlag string, verbose bool) error { fmt.Fprintf(os.Stderr, "Failed to reload server config: %v\n", err) continue } - config, err := file.Config(ctx, verbose) + config, err := file.Config(ctx) if err != nil { fmt.Fprintf(os.Stderr, "Failed to reload server config: %v\n", err) continue diff --git a/internal/http/log.go b/internal/keystore/vault/log.go similarity index 58% rename from internal/http/log.go rename to internal/keystore/vault/log.go index 7eac16a6..7417fe7d 100644 --- a/internal/http/log.go +++ b/internal/keystore/vault/log.go @@ -1,28 +1,16 @@ -package http +package vault import ( "log/slog" "net/http" - "slices" "time" ) -// LoggingTransport is an http.RoundTripper that logs the request and response. -type LoggingTransport struct { +type loggingTransport struct { http.RoundTripper - skipPaths []string } -// NewLoggingTransport creates an http.RoundTripper that logs the request and response. -func NewLoggingTransport(rt http.RoundTripper, skipPaths ...string) *LoggingTransport { - return &LoggingTransport{ - RoundTripper: rt, - skipPaths: skipPaths, - } -} - -// RoundTrip implements the RoundTripper interface. -func (lt *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) { +func (lt *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) { rt := lt.RoundTripper if rt == nil { rt = http.DefaultTransport @@ -32,24 +20,27 @@ func (lt *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) resp, err := rt.RoundTrip(req) // don't log health checks - if !slices.Contains(lt.skipPaths, req.URL.Path) { + if req.URL.Path != "/v1/sys/health" { switch { case err != nil: - slog.Info("HTTP error", + slog.Debug("HTTP error", slog.String("method", req.Method), slog.String("url", req.URL.String()), + slog.String("auth", obfuscateToken(req.Header.Get("X-Vault-Token"))), slog.Duration("duration", time.Since(start)), slog.String("error", err.Error())) case resp.StatusCode >= 300: - slog.Info("HTTP error response", + slog.Debug("HTTP error response", slog.String("method", req.Method), slog.String("url", req.URL.String()), + slog.String("auth", obfuscateToken(req.Header.Get("X-Vault-Token"))), slog.Duration("duration", time.Since(start)), slog.String("status", resp.Status)) default: slog.Debug("HTTP success response", slog.String("method", req.Method), slog.String("url", req.URL.String()), + slog.String("auth", obfuscateToken(req.Header.Get("X-Vault-Token"))), slog.Duration("duration", time.Since(start)), slog.String("status", resp.Status)) } @@ -57,3 +48,14 @@ func (lt *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) return resp, err } + +func obfuscateToken(token string) string { + switch { + case len(token) == 0: + return "" + case len(token) > 8: + return "***" + token[len(token)-4:] + default: + return "***" + } +} diff --git a/internal/keystore/vault/vault.go b/internal/keystore/vault/vault.go index e367f4fd..4e9f7958 100644 --- a/internal/keystore/vault/vault.go +++ b/internal/keystore/vault/vault.go @@ -27,7 +27,6 @@ import ( "aead.dev/mem" vaultapi "github.com/hashicorp/vault/api" "github.com/minio/kes" - xhttp "github.com/minio/kes/internal/http" "github.com/minio/kes/internal/keystore" kesdk "github.com/minio/kms-go/kes" ) @@ -41,7 +40,7 @@ type Store struct { // Connect connects to a Hashicorp Vault server with // the given configuration. -func Connect(ctx context.Context, c *Config, verbose bool) (*Store, error) { +func Connect(ctx context.Context, c *Config) (*Store, error) { c = c.Clone() if c.Engine == "" { @@ -114,8 +113,8 @@ func Connect(ctx context.Context, c *Config, verbose bool) (*Store, error) { tr.DisableKeepAlives = true tr.MaxIdleConnsPerHost = -1 } - if verbose { - config.HttpClient.Transport = xhttp.NewLoggingTransport(config.HttpClient.Transport, "/v1/sys/health") + if slog.Default().Enabled(ctx, slog.LevelDebug) { + config.HttpClient.Transport = &loggingTransport{config.HttpClient.Transport} } vaultClient, err := vaultapi.NewClient(config) if err != nil { @@ -150,14 +149,8 @@ func Connect(ctx context.Context, c *Config, verbose bool) (*Store, error) { lastAuthSuccess = false } } else { - if verbose { - obfuscatedToken := secret.Auth.ClientToken - if len(obfuscatedToken) > 10 { - obfuscatedToken = obfuscatedToken[:2] + "***" + obfuscatedToken[len(obfuscatedToken)-4:] - } else { - obfuscatedToken = "***" - } - slog.Info("Authentication successful", slog.String("token", obfuscatedToken)) + if slog.Default().Enabled(ctx, slog.LevelDebug) { + slog.Debug("Authentication successful", slog.String("token", obfuscateToken(secret.Auth.ClientToken))) } lastAuthSuccess = true } diff --git a/internal/sys/build.go b/internal/sys/build.go index a9e6a502..5390e406 100644 --- a/internal/sys/build.go +++ b/internal/sys/build.go @@ -16,7 +16,7 @@ import ( type BinaryInfo struct { Version string // The version of this binary CommitID string // The git commit hash - Runtime string // The Go runtime version, e.g. go1.21.0 + Runtime string // The Go runtime version, e.g. go1.22.0 Compiler string // The Go compiler used to build this binary } diff --git a/kesconf/aws_test.go b/kesconf/aws_test.go index 3ca722f0..07c20ba7 100644 --- a/kesconf/aws_test.go +++ b/kesconf/aws_test.go @@ -29,7 +29,7 @@ func TestAWS(t *testing.T) { ctx, cancel := testingContext(t) defer cancel() - store, err := config.KeyStore.Connect(ctx, false) + store, err := config.KeyStore.Connect(ctx) if err != nil { t.Fatal(err) } diff --git a/kesconf/azure_test.go b/kesconf/azure_test.go index 55cd021a..06d7e8cf 100644 --- a/kesconf/azure_test.go +++ b/kesconf/azure_test.go @@ -35,7 +35,7 @@ func TestAzure(t *testing.T) { ctx, cancel := testingContext(t) defer cancel() - store, err := config.KeyStore.Connect(ctx, false) + store, err := config.KeyStore.Connect(ctx) if err != nil { t.Fatal(err) } diff --git a/kesconf/config.go b/kesconf/config.go index 67860da2..fdc76d9e 100644 --- a/kesconf/config.go +++ b/kesconf/config.go @@ -66,6 +66,7 @@ type ymlFile struct { Log struct { Error env[string] `yaml:"error"` Audit env[string] `yaml:"audit"` + Level env[string] `yaml:"level"` } `yaml:"log"` Keys []struct { @@ -299,6 +300,10 @@ func ymlToServerConfig(y *ymlFile) (*File, error) { if err != nil { return nil, err } + logLevel, err := parseLogLevel(y.Log.Level.Value) + if err != nil { + return nil, err + } for path, api := range y.API.Paths { if api.Timeout.Value < 0 { @@ -354,6 +359,7 @@ func ymlToServerConfig(y *ymlFile) (*File, error) { Log: &LogConfig{ ErrLevel: errLevel, AuditLevel: auditLevel, + LogLevel: logLevel, }, KeyStore: keystore, } diff --git a/kesconf/file.go b/kesconf/file.go index 2cbd52b6..5bd73dda 100644 --- a/kesconf/file.go +++ b/kesconf/file.go @@ -159,7 +159,7 @@ func (f *File) TLSConfig() (*tls.Config, error) { // Config returns a new KES configuration as specified by // the File. It connects to the KeyStore using the given // context. -func (f *File) Config(ctx context.Context, verbose bool) (*kes.Config, error) { +func (f *File) Config(ctx context.Context) (*kes.Config, error) { conf := &kes.Config{ Admin: f.Admin, } @@ -211,7 +211,7 @@ func (f *File) Config(ctx context.Context, verbose bool) (*kes.Config, error) { } if f.KeyStore != nil { - keystore, err := f.KeyStore.Connect(ctx, verbose) + keystore, err := f.KeyStore.Connect(ctx) if err != nil { return nil, err } @@ -298,6 +298,9 @@ type LogConfig struct { // Audit determines whether the KES server logs audit events to STDOUT. // It does not en/disable audit logging in general. AuditLevel slog.Level + + // Log level for which to report KES diagnostic messages. + LogLevel slog.Level } // APIConfig is a structure that holds the API configuration @@ -365,7 +368,7 @@ type Key struct { type KeyStore interface { // Connect establishes and returns a new connection // to the keystore. - Connect(ctx context.Context, verbose bool) (kes.KeyStore, error) + Connect(ctx context.Context) (kes.KeyStore, error) } // FSKeyStore is a structure containing the configuration @@ -382,7 +385,7 @@ type FSKeyStore struct { } // Connect returns a kv.Store that stores key-value pairs in a path on the filesystem. -func (s *FSKeyStore) Connect(context.Context, bool) (kes.KeyStore, error) { +func (s *FSKeyStore) Connect(context.Context) (kes.KeyStore, error) { return fs.NewStore(s.Path) } @@ -528,7 +531,7 @@ type VaultTransit struct { } // Connect returns a kv.Store that stores key-value pairs on a Hashicorp Vault server. -func (s *VaultKeyStore) Connect(ctx context.Context, verbose bool) (kes.KeyStore, error) { +func (s *VaultKeyStore) Connect(ctx context.Context) (kes.KeyStore, error) { if s.AppRole == nil && s.Kubernetes == nil { return nil, errors.New("edge: failed to connect to hashicorp vault: no authentication method specified") } @@ -568,7 +571,7 @@ func (s *VaultKeyStore) Connect(ctx context.Context, verbose bool) (kes.KeyStore KeyName: s.Transit.KeyName, } } - return vault.Connect(ctx, c, verbose) + return vault.Connect(ctx, c) } // FortanixKeyStore is a structure containing the @@ -594,7 +597,7 @@ type FortanixKeyStore struct { } // Connect returns a kv.Store that stores key-value pairs on a Fortanix SDKMS server. -func (s *FortanixKeyStore) Connect(ctx context.Context, _ bool) (kes.KeyStore, error) { +func (s *FortanixKeyStore) Connect(ctx context.Context) (kes.KeyStore, error) { return fortanix.Connect(ctx, &fortanix.Config{ Endpoint: s.Endpoint, GroupID: s.GroupID, @@ -629,7 +632,7 @@ type KeySecureKeyStore struct { } // Connect returns a kv.Store that stores key-value pairs on a Gemalto KeySecure instance. -func (s *KeySecureKeyStore) Connect(ctx context.Context, _ bool) (kes.KeyStore, error) { +func (s *KeySecureKeyStore) Connect(ctx context.Context) (kes.KeyStore, error) { return gemalto.Connect(ctx, &gemalto.Config{ Endpoint: s.Endpoint, CAPath: s.CAPath, @@ -678,7 +681,7 @@ type GCPSecretManagerKeyStore struct { } // Connect returns a kv.Store that stores key-value pairs on GCP SecretManager. -func (s *GCPSecretManagerKeyStore) Connect(ctx context.Context, _ bool) (kes.KeyStore, error) { +func (s *GCPSecretManagerKeyStore) Connect(ctx context.Context) (kes.KeyStore, error) { return gcp.Connect(ctx, &gcp.Config{ Endpoint: s.Endpoint, ProjectID: s.ProjectID, @@ -722,7 +725,7 @@ type AWSSecretsManagerKeyStore struct { } // Connect returns a kv.Store that stores key-value pairs on AWS SecretsManager. -func (s *AWSSecretsManagerKeyStore) Connect(ctx context.Context, _ bool) (kes.KeyStore, error) { +func (s *AWSSecretsManagerKeyStore) Connect(ctx context.Context) (kes.KeyStore, error) { return aws.Connect(ctx, &aws.Config{ Addr: s.Endpoint, Region: s.Region, @@ -758,7 +761,7 @@ type AzureKeyVaultKeyStore struct { } // Connect returns a kv.Store that stores key-value pairs on Azure KeyVault. -func (s *AzureKeyVaultKeyStore) Connect(_ context.Context, verbose bool) (kes.KeyStore, error) { +func (s *AzureKeyVaultKeyStore) Connect(_ context.Context) (kes.KeyStore, error) { if (s.TenantID != "" || s.ClientID != "" || s.ClientSecret != "") && s.ManagedIdentityClientID != "" { return nil, errors.New("edge: failed to connect to Azure KeyVault: more than one authentication method specified") } @@ -808,7 +811,7 @@ type EntrustKeyControlKeyStore struct { } // Connect returns a kv.Store that stores key-value pairs on Entrust KeyControl. -func (s *EntrustKeyControlKeyStore) Connect(ctx context.Context, _ bool) (kes.KeyStore, error) { +func (s *EntrustKeyControlKeyStore) Connect(ctx context.Context) (kes.KeyStore, error) { var rootCAs *x509.CertPool if s.CAPath != "" { ca, err := https.CertPoolFromFile(s.CAPath) diff --git a/kesconf/fortanix_test.go b/kesconf/fortanix_test.go index 396c3479..3e8ebe85 100644 --- a/kesconf/fortanix_test.go +++ b/kesconf/fortanix_test.go @@ -30,7 +30,7 @@ func TestFortanix(t *testing.T) { ctx, cancel := testingContext(t) defer cancel() - store, err := config.KeyStore.Connect(ctx, false) + store, err := config.KeyStore.Connect(ctx) if err != nil { t.Fatal(err) } diff --git a/kesconf/fs_test.go b/kesconf/fs_test.go index 623b01d4..2336b873 100644 --- a/kesconf/fs_test.go +++ b/kesconf/fs_test.go @@ -24,7 +24,7 @@ func TestFS(t *testing.T) { ctx, cancel := testingContext(t) defer cancel() - store, err := config.Connect(ctx, false) + store, err := config.Connect(ctx) if err != nil { t.Fatal(err) } diff --git a/kesconf/gcp_test.go b/kesconf/gcp_test.go index 5966b79f..f3e6d131 100644 --- a/kesconf/gcp_test.go +++ b/kesconf/gcp_test.go @@ -30,7 +30,7 @@ func TestGCP(t *testing.T) { ctx, cancel := testingContext(t) defer cancel() - store, err := config.KeyStore.Connect(ctx, false) + store, err := config.KeyStore.Connect(ctx) if err != nil { t.Fatal(err) } diff --git a/kesconf/gemalto_test.go b/kesconf/gemalto_test.go index 01909ede..41785706 100644 --- a/kesconf/gemalto_test.go +++ b/kesconf/gemalto_test.go @@ -30,7 +30,7 @@ func TestGemalto(t *testing.T) { ctx, cancel := testingContext(t) defer cancel() - store, err := config.KeyStore.Connect(ctx, false) + store, err := config.KeyStore.Connect(ctx) if err != nil { t.Fatal(err) } diff --git a/kesconf/keycontrol_test.go b/kesconf/keycontrol_test.go index 0d2e1190..e5d10a8d 100644 --- a/kesconf/keycontrol_test.go +++ b/kesconf/keycontrol_test.go @@ -30,7 +30,7 @@ func TestKeyControl(t *testing.T) { ctx, cancel := testingContext(t) defer cancel() - store, err := config.KeyStore.Connect(ctx, false) + store, err := config.KeyStore.Connect(ctx) if err != nil { t.Fatal(err) } diff --git a/kesconf/vault_test.go b/kesconf/vault_test.go index 68998965..00f15d8c 100644 --- a/kesconf/vault_test.go +++ b/kesconf/vault_test.go @@ -30,7 +30,7 @@ func TestVault(t *testing.T) { ctx, cancel := testingContext(t) defer cancel() - store, err := config.KeyStore.Connect(ctx, false) + store, err := config.KeyStore.Connect(ctx) if err != nil { t.Fatal(err) }