diff --git a/auth/iam.go b/auth/iam.go index 90e9240..bcb9ad6 100644 --- a/auth/iam.go +++ b/auth/iam.go @@ -107,42 +107,45 @@ var ( ) type Opts struct { - RootAccount Account - Dir string - LDAPServerURL string - LDAPBindDN string - LDAPPassword string - LDAPQueryBase string - LDAPObjClasses string - LDAPAccessAtr string - LDAPSecretAtr string - LDAPRoleAtr string - LDAPUserIdAtr string - LDAPGroupIdAtr string - VaultEndpointURL string - VaultSecretStoragePath string - VaultAuthMethod string - VaultMountPath string - VaultRootToken string - VaultRoleId string - VaultRoleSecret string - VaultServerCert string - VaultClientCert string - VaultClientCertKey string - S3Access string - S3Secret string - S3Region string - S3Bucket string - S3Endpoint string - S3DisableSSlVerfiy bool - CacheDisable bool - CacheTTL int - CachePrune int - IpaHost string - IpaVaultName string - IpaUser string - IpaPassword string - IpaInsecure bool + RootAccount Account + Dir string + LDAPServerURL string + LDAPBindDN string + LDAPPassword string + LDAPQueryBase string + LDAPObjClasses string + LDAPAccessAtr string + LDAPSecretAtr string + LDAPRoleAtr string + LDAPUserIdAtr string + LDAPGroupIdAtr string + VaultEndpointURL string + VaultNamespace string + VaultSecretStoragePath string + VaultSecretStorageNamespace string + VaultAuthMethod string + VaultAuthNamespace string + VaultMountPath string + VaultRootToken string + VaultRoleId string + VaultRoleSecret string + VaultServerCert string + VaultClientCert string + VaultClientCertKey string + S3Access string + S3Secret string + S3Region string + S3Bucket string + S3Endpoint string + S3DisableSSlVerfiy bool + CacheDisable bool + CacheTTL int + CachePrune int + IpaHost string + IpaVaultName string + IpaUser string + IpaPassword string + IpaInsecure bool } func New(o *Opts) (IAMService, error) { @@ -164,8 +167,8 @@ func New(o *Opts) (IAMService, error) { fmt.Printf("initializing S3 IAM with '%v/%v'\n", o.S3Endpoint, o.S3Bucket) case o.VaultEndpointURL != "": - svc, err = NewVaultIAMService(o.RootAccount, o.VaultEndpointURL, o.VaultSecretStoragePath, - o.VaultAuthMethod, o.VaultMountPath, o.VaultRootToken, o.VaultRoleId, o.VaultRoleSecret, + svc, err = NewVaultIAMService(o.RootAccount, o.VaultEndpointURL, o.VaultNamespace, o.VaultSecretStoragePath, o.VaultSecretStorageNamespace, + o.VaultAuthMethod, o.VaultAuthNamespace, o.VaultMountPath, o.VaultRootToken, o.VaultRoleId, o.VaultRoleSecret, o.VaultServerCert, o.VaultClientCert, o.VaultClientCertKey) fmt.Printf("initializing Vault IAM with %q\n", o.VaultEndpointURL) case o.IpaHost != "": diff --git a/auth/iam_vault.go b/auth/iam_vault.go index cfdde4b..98c8149 100644 --- a/auth/iam_vault.go +++ b/auth/iam_vault.go @@ -38,15 +38,39 @@ type VaultIAMService struct { creds schema.AppRoleLoginRequest } +type VaultIAMNamespace struct { + Auth string + SecretStorage string +} + +// Resolve empty specific namespaces to the fallback. +// Empty result means root namespace. +func resolveVaultNamespaces(authNamespace, secretStorageNamespace, fallback string) VaultIAMNamespace { + ns := VaultIAMNamespace{ + Auth: authNamespace, + SecretStorage: secretStorageNamespace, + } + + if ns.Auth == "" { + ns.Auth = fallback + } + if ns.SecretStorage == "" { + ns.SecretStorage = fallback + } + + return ns +} + var _ IAMService = &VaultIAMService{} -func NewVaultIAMService(rootAcc Account, endpoint, secretStoragePath, - authMethod, mountPath, rootToken, roleID, roleSecret, serverCert, +func NewVaultIAMService(rootAcc Account, endpoint, namespace, secretStoragePath, secretStorageNamespace, + authMethod, authNamespace, mountPath, rootToken, roleID, roleSecret, serverCert, clientCert, clientCertKey string) (IAMService, error) { opts := []vault.ClientOption{ vault.WithAddress(endpoint), vault.WithRequestTimeout(requestTimeout), } + if serverCert != "" { tls := vault.TLSConfiguration{} @@ -80,6 +104,28 @@ func NewVaultIAMService(rootAcc Account, endpoint, secretStoragePath, kvReqOpts = append(kvReqOpts, vault.WithMountPath(mountPath)) } + // Resolve namespaces using optional generic fallback "namespace" + ns := resolveVaultNamespaces(authNamespace, secretStorageNamespace, namespace) + + // Guard: AppRole tokens are namespace scoped. If using AppRole and namespaces differ, error early. + // Root token can span namespaces because each request carries X-Vault-Namespace. + if rootToken == "" && ns.Auth != "" && ns.SecretStorage != "" && ns.Auth != ns.SecretStorage { + return nil, fmt.Errorf( + "approle tokens are namespace scoped. auth namespace %q and secret storage namespace %q differ. "+ + "use the same namespace or authenticate with a root token", + ns.Auth, ns.SecretStorage, + ) + } + + // Apply namespaces to the correct request option sets. + // For root token we do not need an auth namespace since we are not logging in via auth. + if rootToken == "" && ns.Auth != "" { + authReqOpts = append(authReqOpts, vault.WithNamespace(ns.Auth)) + } + if ns.SecretStorage != "" { + kvReqOpts = append(kvReqOpts, vault.WithNamespace(ns.SecretStorage)) + } + creds := schema.AppRoleLoginRequest{ RoleId: roleID, SecretId: roleSecret, @@ -179,6 +225,10 @@ func (vt *VaultIAMService) CreateAccount(account Account) error { if strings.Contains(err.Error(), "check-and-set") { return ErrUserExists } + if vault.IsErrorStatus(err, http.StatusForbidden) { + return fmt.Errorf("vault 403 permission denied on path %q. check KV mount path and policy. original: %w", + vt.secretStoragePath+"/"+account.Access, err) + } return err } return nil diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index 6d40588..815c499 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -37,51 +37,54 @@ import ( ) var ( - port, admPort string - rootUserAccess string - rootUserSecret string - region string - admCertFile, admKeyFile string - certFile, keyFile string - kafkaURL, kafkaTopic, kafkaKey string - natsURL, natsTopic string - rabbitmqURL, rabbitmqExchange string - rabbitmqRoutingKey string - eventWebhookURL string - eventConfigFilePath string - logWebhookURL, accessLog string - adminLogFile string - healthPath string - virtualDomain string - debug bool - keepAlive bool - pprof string - quiet bool - readonly bool - iamDir string - ldapURL, ldapBindDN, ldapPassword string - ldapQueryBase, ldapObjClasses string - ldapAccessAtr, ldapSecAtr, ldapRoleAtr string - ldapUserIdAtr, ldapGroupIdAtr string - vaultEndpointURL, vaultSecretStoragePath string - vaultAuthMethod, vaultMountPath string - vaultRootToken, vaultRoleId string - vaultRoleSecret, vaultServerCert string - vaultClientCert, vaultClientCertKey string - s3IamAccess, s3IamSecret string - s3IamRegion, s3IamBucket string - s3IamEndpoint string - s3IamSslNoVerify bool - iamCacheDisable bool - iamCacheTTL int - iamCachePrune int - metricsService string - statsdServers string - dogstatsServers string - ipaHost, ipaVaultName string - ipaUser, ipaPassword string - ipaInsecure bool - iamDebug bool + port, admPort string + rootUserAccess string + rootUserSecret string + region string + admCertFile, admKeyFile string + certFile, keyFile string + kafkaURL, kafkaTopic, kafkaKey string + natsURL, natsTopic string + rabbitmqURL, rabbitmqExchange string + rabbitmqRoutingKey string + eventWebhookURL string + eventConfigFilePath string + logWebhookURL, accessLog string + adminLogFile string + healthPath string + virtualDomain string + debug bool + keepAlive bool + pprof string + quiet bool + readonly bool + iamDir string + ldapURL, ldapBindDN, ldapPassword string + ldapQueryBase, ldapObjClasses string + ldapAccessAtr, ldapSecAtr, ldapRoleAtr string + ldapUserIdAtr, ldapGroupIdAtr string + vaultEndpointURL, vaultNamespace string + vaultSecretStoragePath string + vaultSecretStorageNamespace string + vaultAuthMethod, vaultAuthNamespace string + vaultMountPath string + vaultRootToken, vaultRoleId string + vaultRoleSecret, vaultServerCert string + vaultClientCert, vaultClientCertKey string + s3IamAccess, s3IamSecret string + s3IamRegion, s3IamBucket string + s3IamEndpoint string + s3IamSslNoVerify bool + iamCacheDisable bool + iamCacheTTL int + iamCachePrune int + metricsService string + statsdServers string + dogstatsServers string + ipaHost, ipaVaultName string + ipaUser, ipaPassword string + ipaInsecure bool + iamDebug bool ) var ( @@ -405,18 +408,36 @@ func initFlags() []cli.Flag { EnvVars: []string{"VGW_IAM_VAULT_ENDPOINT_URL"}, Destination: &vaultEndpointURL, }, + &cli.StringFlag{ + Name: "iam-vault-namespace", + Usage: "vault server namespace", + EnvVars: []string{"VGW_IAM_VAULT_NAMESPACE"}, + Destination: &vaultNamespace, + }, &cli.StringFlag{ Name: "iam-vault-secret-storage-path", Usage: "vault server secret storage path", EnvVars: []string{"VGW_IAM_VAULT_SECRET_STORAGE_PATH"}, Destination: &vaultSecretStoragePath, }, + &cli.StringFlag{ + Name: "iam-vault-secret-storage-namespace", + Usage: "vault server secret storage namespace", + EnvVars: []string{"VGW_IAM_VAULT_SECRET_STORAGE_NAMESPACE"}, + Destination: &vaultSecretStorageNamespace, + }, &cli.StringFlag{ Name: "iam-vault-auth-method", Usage: "vault server auth method", EnvVars: []string{"VGW_IAM_VAULT_AUTH_METHOD"}, Destination: &vaultAuthMethod, }, + &cli.StringFlag{ + Name: "iam-vault-auth-namespace", + Usage: "vault server auth namespace", + EnvVars: []string{"VGW_IAM_VAULT_AUTH_NAMESPACE"}, + Destination: &vaultAuthNamespace, + }, &cli.StringFlag{ Name: "iam-vault-mount-path", Usage: "vault server mount path", @@ -650,41 +671,44 @@ func runGateway(ctx context.Context, be backend.Backend) error { Secret: rootUserSecret, Role: auth.RoleAdmin, }, - Dir: iamDir, - LDAPServerURL: ldapURL, - LDAPBindDN: ldapBindDN, - LDAPPassword: ldapPassword, - LDAPQueryBase: ldapQueryBase, - LDAPObjClasses: ldapObjClasses, - LDAPAccessAtr: ldapAccessAtr, - LDAPSecretAtr: ldapSecAtr, - LDAPRoleAtr: ldapRoleAtr, - LDAPUserIdAtr: ldapUserIdAtr, - LDAPGroupIdAtr: ldapGroupIdAtr, - VaultEndpointURL: vaultEndpointURL, - VaultSecretStoragePath: vaultSecretStoragePath, - VaultAuthMethod: vaultAuthMethod, - VaultMountPath: vaultMountPath, - VaultRootToken: vaultRootToken, - VaultRoleId: vaultRoleId, - VaultRoleSecret: vaultRoleSecret, - VaultServerCert: vaultServerCert, - VaultClientCert: vaultClientCert, - VaultClientCertKey: vaultClientCertKey, - S3Access: s3IamAccess, - S3Secret: s3IamSecret, - S3Region: s3IamRegion, - S3Bucket: s3IamBucket, - S3Endpoint: s3IamEndpoint, - S3DisableSSlVerfiy: s3IamSslNoVerify, - CacheDisable: iamCacheDisable, - CacheTTL: iamCacheTTL, - CachePrune: iamCachePrune, - IpaHost: ipaHost, - IpaVaultName: ipaVaultName, - IpaUser: ipaUser, - IpaPassword: ipaPassword, - IpaInsecure: ipaInsecure, + Dir: iamDir, + LDAPServerURL: ldapURL, + LDAPBindDN: ldapBindDN, + LDAPPassword: ldapPassword, + LDAPQueryBase: ldapQueryBase, + LDAPObjClasses: ldapObjClasses, + LDAPAccessAtr: ldapAccessAtr, + LDAPSecretAtr: ldapSecAtr, + LDAPRoleAtr: ldapRoleAtr, + LDAPUserIdAtr: ldapUserIdAtr, + LDAPGroupIdAtr: ldapGroupIdAtr, + VaultEndpointURL: vaultEndpointURL, + VaultNamespace: vaultNamespace, + VaultSecretStoragePath: vaultSecretStoragePath, + VaultSecretStorageNamespace: vaultSecretStorageNamespace, + VaultAuthMethod: vaultAuthMethod, + VaultAuthNamespace: vaultAuthNamespace, + VaultMountPath: vaultMountPath, + VaultRootToken: vaultRootToken, + VaultRoleId: vaultRoleId, + VaultRoleSecret: vaultRoleSecret, + VaultServerCert: vaultServerCert, + VaultClientCert: vaultClientCert, + VaultClientCertKey: vaultClientCertKey, + S3Access: s3IamAccess, + S3Secret: s3IamSecret, + S3Region: s3IamRegion, + S3Bucket: s3IamBucket, + S3Endpoint: s3IamEndpoint, + S3DisableSSlVerfiy: s3IamSslNoVerify, + CacheDisable: iamCacheDisable, + CacheTTL: iamCacheTTL, + CachePrune: iamCachePrune, + IpaHost: ipaHost, + IpaVaultName: ipaVaultName, + IpaUser: ipaUser, + IpaPassword: ipaPassword, + IpaInsecure: ipaInsecure, }) if err != nil { return fmt.Errorf("setup iam: %w", err)