Skip to content

Commit

Permalink
Support authenticating to the root namespace from within an auth_logi…
Browse files Browse the repository at this point in the history
…n* (#2066)

* Add new auth_login field for enforcing root namespace

The additional field is a work around to deal with TF plugin SDK's
evaluation of values in boolean context. There was no way to treat the
"" as the default Vault namespace from the TF schema, since none of its
Get*() public methods do what we need. So the new way is to set
is_root_namespace to true, leave the namespace field unset in any
auth_login* resource blocks.

---------

Co-authored-by: Vinay Gopalan <[email protected]>
  • Loading branch information
benashz and vinay-gopalan authored Oct 25, 2023
1 parent b740f13 commit bff5e91
Show file tree
Hide file tree
Showing 28 changed files with 362 additions and 99 deletions.
1 change: 1 addition & 0 deletions internal/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
FieldParameters = "parameters"
FieldMethod = "method"
FieldNamespace = "namespace"
FieldUseRootNamespace = "use_root_namespace"
FieldNamespaceID = "namespace_id"
FieldNamespacePath = "namespace_path"
FieldPathFQ = "path_fq"
Expand Down
63 changes: 52 additions & 11 deletions internal/provider/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ type AuthLogin interface {
LoginPath() string
Method() string
Login(*api.Client) (*api.Secret, error)
Namespace() string
Namespace() (string, bool)
Params() map[string]interface{}
}

Expand Down Expand Up @@ -149,14 +149,18 @@ func (l *AuthLoginCommon) Init(d *schema.ResourceData, authField string, validat
return l.validate()
}

func (l *AuthLoginCommon) Namespace() string {
func (l *AuthLoginCommon) Namespace() (string, bool) {
if l.params != nil {
ns, ok := l.params[consts.FieldNamespace].(string)
if ok {
return ns
if v, ok := l.params[consts.FieldUseRootNamespace]; ok && v.(bool) {
return "", true
}

if ns, ok := l.params[consts.FieldNamespace]; ok && ns.(string) != "" {
return ns.(string), true
}

}
return ""
return "", false
}

func (l *AuthLoginCommon) MountPath() string {
Expand Down Expand Up @@ -246,6 +250,12 @@ func (l *AuthLoginCommon) init(d *schema.ResourceData) (string, map[string]inter
var params map[string]interface{}
if v, ok := l.getOk(d, consts.FieldParameters); ok {
params = v.(map[string]interface{})
ns, _ := l.getOk(d, consts.FieldNamespace)
params[consts.FieldNamespace] = ns

if v := l.get(d, consts.FieldUseRootNamespace); v != nil {
params[consts.FieldUseRootNamespace] = v
}
} else {
v := config[0]
if v == nil {
Expand Down Expand Up @@ -291,7 +301,15 @@ func (l *AuthLoginCommon) checkFieldsOneOf(d *schema.ResourceData, fields ...str
}

func (l *AuthLoginCommon) getOk(d *schema.ResourceData, field string) (interface{}, bool) {
return d.GetOk(fmt.Sprintf("%s.0.%s", l.authField, field))
return d.GetOk(l.fieldPath(d, field))
}

func (l *AuthLoginCommon) get(d *schema.ResourceData, field string) interface{} {
return d.Get(l.fieldPath(d, field))
}

func (l *AuthLoginCommon) fieldPath(d *schema.ResourceData, field string) string {
return fmt.Sprintf("%s.0.%s", l.authField, field)
}

func (l *AuthLoginCommon) validate() error {
Expand Down Expand Up @@ -320,12 +338,35 @@ func GetAuthLogin(r *schema.ResourceData) (AuthLogin, error) {
return nil, nil
}

func mustAddLoginSchema(r *schema.Resource, defaultMount string) *schema.Resource {
func mustAddLoginSchema(r *schema.Resource, authField string, defaultMount string) *schema.Resource {
m := map[string]*schema.Schema{
consts.FieldNamespace: {
Type: schema.TypeString,
Optional: true,
Description: "The authentication engine's namespace.",
Type: schema.TypeString,
Optional: true,
Description: fmt.Sprintf(
"The authentication engine's namespace. Conflicts with %s",
consts.FieldUseRootNamespace,
),
ConflictsWith: []string{
fmt.Sprintf("%s.0.%s",
authField,
consts.FieldUseRootNamespace,
),
},
},
consts.FieldUseRootNamespace: {
Type: schema.TypeBool,
Optional: true,
Description: fmt.Sprintf(
"Authenticate to the root Vault namespace. Conflicts with %s",
consts.FieldNamespace,
),
ConflictsWith: []string{
fmt.Sprintf("%s.0.%s",
authField,
consts.FieldNamespace,
),
},
},
}

Expand Down
2 changes: 1 addition & 1 deletion internal/provider/auth_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func GetAWSLoginSchemaResource(authField string) *schema.Resource {
Description: `The Vault header value to include in the STS signing request.`,
},
},
}, consts.MountTypeAWS)
}, authField, consts.MountTypeAWS)
}

var _ AuthLogin = (*AuthLoginAWS)(nil)
Expand Down
1 change: 1 addition & 0 deletions internal/provider/auth_aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func TestAuthLoginAWS_Init(t *testing.T) {
},
expectParams: map[string]interface{}{
consts.FieldNamespace: "ns1",
consts.FieldUseRootNamespace: false,
consts.FieldRole: "alice",
consts.FieldMount: consts.MountTypeAWS,
consts.FieldAWSAccessKeyID: "key-id",
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/auth_azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func GetAzureLoginSchemaResource(authField string) *schema.Resource {
ConflictsWith: []string{fmt.Sprintf("%s.0.%s", authField, consts.FieldJWT)},
},
},
}, consts.MountTypeAzure)
}, authField, consts.MountTypeAzure)
}

var _ AuthLogin = (*AuthLoginAzure)(nil)
Expand Down
1 change: 1 addition & 0 deletions internal/provider/auth_azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func TestAuthLoginAzure_Init(t *testing.T) {
},
expectParams: map[string]interface{}{
consts.FieldNamespace: "ns1",
consts.FieldUseRootNamespace: false,
consts.FieldMount: consts.MountTypeAzure,
consts.FieldRole: "alice",
consts.FieldJWT: "jwt1",
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/auth_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func GetCertLoginSchemaResource(authField string) *schema.Resource {
Description: "Path to a file containing the private key that the certificate was issued for.",
},
},
}, consts.MountTypeCert)
}, authField, consts.MountTypeCert)
}

var _ AuthLogin = (*AuthLoginCert)(nil)
Expand Down
56 changes: 30 additions & 26 deletions internal/provider/auth_cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ func TestAuthLoginCert_Init(t *testing.T) {
},
authField: consts.FieldAuthLoginCert,
expectParams: map[string]interface{}{
consts.FieldNamespace: "",
consts.FieldMount: consts.MountTypeCert,
consts.FieldName: "",
consts.FieldCACertFile: "ca.crt",
consts.FieldCertFile: "cert.crt",
consts.FieldKeyFile: "cert.key",
consts.FieldNamespace: "",
consts.FieldUseRootNamespace: false,
consts.FieldMount: consts.MountTypeCert,
consts.FieldName: "",
consts.FieldCACertFile: "ca.crt",
consts.FieldCertFile: "cert.crt",
consts.FieldKeyFile: "cert.key",
},
wantErr: false,
},
Expand All @@ -75,11 +76,12 @@ func TestAuthLoginCert_Init(t *testing.T) {
},
authField: consts.FieldAuthLoginCert,
expectParams: map[string]interface{}{
consts.FieldNamespace: "",
consts.FieldMount: consts.MountTypeCert,
consts.FieldName: "bob",
consts.FieldCertFile: "cert.crt",
consts.FieldKeyFile: "cert.key",
consts.FieldNamespace: "",
consts.FieldUseRootNamespace: false,
consts.FieldMount: consts.MountTypeCert,
consts.FieldName: "bob",
consts.FieldCertFile: "cert.crt",
consts.FieldKeyFile: "cert.key",
},
wantErr: false,
},
Expand All @@ -97,12 +99,13 @@ func TestAuthLoginCert_Init(t *testing.T) {
},
authField: consts.FieldAuthLoginCert,
expectParams: map[string]interface{}{
consts.FieldNamespace: "ns1",
consts.FieldMount: consts.MountTypeCert,
consts.FieldName: "",
consts.FieldCACertFile: "ca.crt",
consts.FieldCertFile: "cert.crt",
consts.FieldKeyFile: "cert.key",
consts.FieldNamespace: "ns1",
consts.FieldUseRootNamespace: false,
consts.FieldMount: consts.MountTypeCert,
consts.FieldName: "",
consts.FieldCACertFile: "ca.crt",
consts.FieldCertFile: "cert.crt",
consts.FieldKeyFile: "cert.key",
},
wantErr: false,
},
Expand All @@ -125,15 +128,16 @@ func TestAuthLoginCert_Init(t *testing.T) {
},
authField: consts.FieldAuthLoginCert,
expectParams: map[string]interface{}{
consts.FieldCACertDir: "/foo/baz",
consts.FieldSkipTLSVerify: true,
consts.FieldTLSServerName: "baz.biff",
consts.FieldNamespace: "ns1",
consts.FieldMount: "cert1",
consts.FieldName: "bob",
consts.FieldCACertFile: "ca.crt",
consts.FieldCertFile: "cert.crt",
consts.FieldKeyFile: "cert.key",
consts.FieldNamespace: "ns1",
consts.FieldUseRootNamespace: false,
consts.FieldCACertDir: "/foo/baz",
consts.FieldSkipTLSVerify: true,
consts.FieldTLSServerName: "baz.biff",
consts.FieldMount: "cert1",
consts.FieldName: "bob",
consts.FieldCACertFile: "ca.crt",
consts.FieldCertFile: "cert.crt",
consts.FieldKeyFile: "cert.key",
},
wantErr: false,
},
Expand Down
3 changes: 2 additions & 1 deletion internal/provider/auth_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func GetGCPLoginSchemaResource(authField string) *schema.Resource {
ConflictsWith: []string{fmt.Sprintf("%s.0.%s", authField, consts.FieldJWT)},
},
},
}, consts.MountTypeGCP)
}, authField, consts.MountTypeGCP)
}

var _ AuthLogin = (*AuthLoginGCP)(nil)
Expand Down Expand Up @@ -120,6 +120,7 @@ func (l *AuthLoginGCP) Login(client *api.Client) (*api.Secret, error) {
}

params, err := l.copyParamsExcluding(
consts.FieldUseRootNamespace,
consts.FieldNamespace,
consts.FieldMount,
consts.FieldJWT,
Expand Down
31 changes: 11 additions & 20 deletions internal/provider/auth_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,16 @@ func GetGenericLoginSchema(authField string) *schema.Schema {
}

func GetGenericLoginSchemaResource(_ string) *schema.Resource {
return &schema.Resource{
return mustAddLoginSchema(&schema.Resource{
Schema: map[string]*schema.Schema{
consts.FieldPath: {
Type: schema.TypeString,
Required: true,
},
consts.FieldNamespace: {
Type: schema.TypeString,
Optional: true,
},
consts.FieldParameters: {
Type: schema.TypeMap,
Optional: true,
Type: schema.TypeMap,
Optional: true,
Sensitive: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Expand All @@ -55,7 +52,7 @@ func GetGenericLoginSchemaResource(_ string) *schema.Resource {
Optional: true,
},
},
}
}, consts.FieldAuthLoginGeneric, consts.MountTypeNone)
}

var _ AuthLogin = (*AuthLoginGeneric)(nil)
Expand All @@ -65,13 +62,8 @@ var _ AuthLogin = (*AuthLoginGeneric)(nil)
// Requires configuration provided by SchemaLoginGeneric.
type AuthLoginGeneric struct {
AuthLoginCommon
path string
namespace string
method string
}

func (l *AuthLoginGeneric) Namespace() string {
return l.namespace
path string
method string
}

func (l *AuthLoginGeneric) Init(d *schema.ResourceData, authField string) (AuthLogin, error) {
Expand All @@ -85,10 +77,6 @@ func (l *AuthLoginGeneric) Init(d *schema.ResourceData, authField string) (AuthL
l.path = path
l.params = params

if v, ok := l.getOk(d, consts.FieldNamespace); ok {
l.namespace = v.(string)
}

if v, ok := l.getOk(d, consts.FieldMethod); ok {
l.method = v.(string)
}
Expand All @@ -109,7 +97,10 @@ func (l *AuthLoginGeneric) Login(client *api.Client) (*api.Secret, error) {
return nil, err
}

params, err := l.copyParams()
params, err := l.copyParamsExcluding(
consts.FieldNamespace,
consts.FieldUseRootNamespace,
)
if err != nil {
return nil, err
}
Expand Down
61 changes: 61 additions & 0 deletions internal/provider/auth_generic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"testing"

"github.com/hashicorp/terraform-provider-vault/internal/consts"
)

func TestAuthLoginGeneric_Namespace(t *testing.T) {
tests := []struct {
name string
params map[string]interface{}
want string
exists bool
}{
{
name: "root-ns",
params: map[string]interface{}{
consts.FieldUseRootNamespace: true,
},
want: "",
exists: true,
},
{
name: "other-ns",
params: map[string]interface{}{
consts.FieldNamespace: "ns1",
},
want: "ns1",
exists: true,
},
{
name: "empty-ns",
params: map[string]interface{}{
consts.FieldNamespace: "",
},
want: "",
exists: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &AuthLoginGeneric{
AuthLoginCommon: AuthLoginCommon{
params: tt.params,
initialized: true,
},
}
got, exists := l.Namespace()
if got != tt.want {
t.Errorf("Namespace() got = %v, want %v", got, tt.want)
}
if exists != tt.exists {
t.Errorf("Namespace() exists = %v, want %v", exists, tt.exists)
}
})
}
}
Loading

0 comments on commit bff5e91

Please sign in to comment.