Skip to content

Commit

Permalink
Use default auth method if no auth method ID is provided for provider
Browse files Browse the repository at this point in the history
  • Loading branch information
elimt committed Apr 25, 2023
1 parent 686ca68 commit 4c40ab4
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 4 deletions.
13 changes: 13 additions & 0 deletions examples/provider/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ provider "boundary" {
password_auth_method_login_name = "myuser" # changeme
password_auth_method_password = "passpass" # changeme
}

provider "boundary" {
addr = "http://127.0.0.1:9200"
password_auth_method_login_name = "myuser"
password_auth_method_password = "passpass"
}

provider "boundary" {
addr = "http://127.0.0.1:9200"
password_auth_method_login_name = "myuser"
password_auth_method_password = "passpass"
scope_id = "s_1234567890"
}
73 changes: 69 additions & 4 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import (
kms_plugin_assets "github.com/hashicorp/terraform-provider-boundary/plugins/kms"
)

const (
PASSWORD_AUTH_METHOD_PREFIX = "ampw"
DEFAULT_PROVIDER_SCOPE = "global"
)

func init() {
// descriptions are written in markdown for docs
schema.DescriptionKind = schema.StringMarkdown
Expand Down Expand Up @@ -69,6 +74,11 @@ func New() *schema.Provider {
Optional: true,
Description: `Specifies a directory that the Boundary provider can use to write and execute its built-in plugins.`,
},
"scope_id": {
Type: schema.TypeString,
Optional: true,
Description: `The scope ID for the default auth method.`,
},
},
ResourcesMap: map[string]*schema.Resource{
"boundary_account": resourceAccount(),
Expand Down Expand Up @@ -117,13 +127,31 @@ type metaData struct {

func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaData) error {
var credentials map[string]interface{}
providerScope := DEFAULT_PROVIDER_SCOPE
amClient := authmethods.NewClient(md.client)

authMethodId, authMethodIdOk := d.GetOk("auth_method_id")

scopeId, scopeIdOk := d.GetOk("scope_id")
if scopeIdOk {
providerScope = scopeId.(string)
}

recoveryKmsHcl, recoveryKmsHclOk := d.GetOk("recovery_kms_hcl")
if token, ok := d.GetOk("token"); ok {
md.client.SetToken(token.(string))
}

// If auth_method_id is not set, get the default auth method ID for the given scope ID
if !authMethodIdOk {
defaultAuthMethodId, err := getDefaultAuthMethodId(ctx, amClient, providerScope, PASSWORD_AUTH_METHOD_PREFIX)
if err != nil {
return err
}
authMethodIdOk = true
authMethodId = defaultAuthMethodId
}

switch {
case recoveryKmsHclOk:
recoveryHclStr, _, err := ReadPathOrContents(recoveryKmsHcl.(string))
Expand Down Expand Up @@ -176,14 +204,14 @@ func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaD
"login_name": authMethodLoginName,
"password": authMethodPassword,
}

case strings.HasPrefix(authMethodId.(string), "amoidc"):
// OIDC-style
return errors.New("OIDC auth method is currently not supported by Boundary Terraform Provider. only password auth method is supported at this time")
default:
return errors.New("no suitable typed auth method information found")
}

am := authmethods.NewClient(md.client)

at, err := am.Authenticate(ctx, authMethodId.(string), "login", credentials)
at, err := amClient.Authenticate(ctx, authMethodId.(string), "login", credentials)
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
statusCode := apiErr.Response().StatusCode()
Expand Down Expand Up @@ -240,3 +268,40 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
return md, nil
}
}

// getDefaultAuthMethodId iterates over boundary client.List() to find the default auth method ID for the given scopeId.
// If there is only one auth method, it'll return it even if it's not the primary auth method
// If scope ID is empty or no primary auth method is found, it returns an error.
func getDefaultAuthMethodId(ctx context.Context, client *authmethods.Client, scopeId, amType string) (string, error) {
if scopeId == "" {
return "", fmt.Errorf("must pass a non empty scope ID string to get default auth method ID")
}
authMethodListResult, err := client.List(ctx, scopeId)
if err != nil {
return "", err
}

authMethodItems := authMethodListResult.GetItems()

// If there is only one auth method that mataches auth method prefix, return it even if it's not the primary auth method
if len(authMethodItems) == 1 {
authMethod := authMethodItems[0]
if !strings.HasPrefix(authMethod.Id, amType) {
return "", fmt.Errorf("error looking up default auth method for scope ID: '%s'. got '%s' but the provider requires an auth method prefix of '%s'", scopeId, authMethod.Id, amType)
}

return authMethod.Id, nil
}

// find the primary auth method that matches auth method prefix
for _, m := range authMethodItems {
if m.IsPrimary {
if !strings.HasPrefix(m.Id, amType) {
return "", fmt.Errorf("error looking up primary auth method for scope ID: '%s'. got '%s' but the provider requires an auth method prefix of '%s'", scopeId, m.Id, amType)
}

return m.Id, nil
}
}
return "", fmt.Errorf("default auth method not found for scope ID: '%s'. Please set a primary auth method on this scope or pass one explicitly using the `auth_method_id` field", scopeId)
}
106 changes: 106 additions & 0 deletions internal/provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/rand"
"encoding/base64"
"fmt"
"regexp"
"strings"
"testing"

Expand All @@ -16,6 +17,7 @@ import (
"github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

var (
Expand Down Expand Up @@ -98,6 +100,42 @@ provider "boundary" {
return strings.Join(c, "\n")
}

func testConfigWithDefaultAuthMethod(url string, res ...string) string {
provider := fmt.Sprintf(`
provider "boundary" {
addr = "%s"
password_auth_method_login_name = "%s"
password_auth_method_password = "%s"
}`, url, tcLoginName, tcPassword)

c := []string{provider}
c = append(c, res...)
return strings.Join(c, "\n")
}

func testConfigWithOIDCAuthMethod(url string, res ...string) string {
provider := fmt.Sprintf(`
provider "boundary" {
addr = "%s"
auth_method_id = "amoidc_0000000000"
}`, url)

c := []string{provider}
c = append(c, res...)
return strings.Join(c, "\n")
}

func testConfigWithoutAMPWCredentials(url string, res ...string) string {
provider := fmt.Sprintf(`
provider "boundary" {
addr = "%s"
}`, url)

c := []string{provider}
c = append(c, res...)
return strings.Join(c, "\n")
}

func testConfigWithRecovery(url string, res ...string) string {
provider := fmt.Sprintf(`
provider "boundary" {
Expand Down Expand Up @@ -139,3 +177,71 @@ func TestProvider(t *testing.T) {
t.Fatalf("err: %s", err)
}
}

func TestConfigWithDefaultAuthMethod(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]

var provider *schema.Provider
resource.Test(t, resource.TestCase{
IsUnitTest: true,
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckScopeResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
Config: testConfigWithDefaultAuthMethod(url, fooOrg, firstProjectFoo, secondProject),
Check: resource.ComposeTestCheckFunc(
testAccCheckScopeResourceExists(provider, "boundary_scope.org1"),
testProviderTokenExists(provider),
),
},
},
})
}

func TestConfigWithoutAMPWCredentials(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]

var provider *schema.Provider
resource.Test(t, resource.TestCase{
IsUnitTest: true,
ProviderFactories: providerFactories(&provider),
Steps: []resource.TestStep{
{
Config: testConfigWithoutAMPWCredentials(url, fooOrg, firstProjectFoo, secondProject),
ExpectError: regexp.MustCompile("password-style auth method login name not set, please set password_auth_method_login_name on the provider"),
},
},
})
}

func TestConfigWithOIDCAuthMethod(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]

var provider *schema.Provider
resource.Test(t, resource.TestCase{
IsUnitTest: true,
ProviderFactories: providerFactories(&provider),
Steps: []resource.TestStep{
{
Config: testConfigWithOIDCAuthMethod(url, fooOrg, firstProjectFoo, secondProject),
ExpectError: regexp.MustCompile("OIDC auth method is currently not supported by Boundary Terraform Provider. only password auth method is supported at this time"),
},
},
})
}

func testProviderTokenExists(testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
md := testProvider.Meta().(*metaData)
if md.client.Token() == "" {
return fmt.Errorf("token not set")
}
return nil
}
}

0 comments on commit 4c40ab4

Please sign in to comment.