Files
terraform-provider-threshold/internal/provider/provider.go
T

334 lines
13 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2020-05-14 17:06:23 -07:00
package provider
import (
2020-05-29 09:13:31 -07:00
"context"
2020-07-12 15:09:40 -07:00
"errors"
2020-09-15 19:56:43 -04:00
"fmt"
"net/http"
2020-09-15 19:56:43 -04:00
"strings"
2020-05-29 09:13:31 -07:00
2020-08-13 11:18:36 -07:00
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
2020-09-15 19:56:43 -04:00
"github.com/hashicorp/boundary/sdk/wrapper"
2022-05-13 16:30:40 -07:00
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-secure-stdlib/configutil/v2"
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
2020-09-15 19:56:43 -04:00
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2022-07-27 16:08:17 -04:00
kms_plugin_assets "github.com/hashicorp/terraform-provider-boundary/plugins/kms"
2020-05-14 17:06:23 -07:00
)
const (
PASSWORD_AUTH_METHOD_PREFIX = "ampw"
LDAP_AUTH_METHOD_PREFIX = "amldap"
DEFAULT_PROVIDER_SCOPE = "global"
)
2020-10-06 20:34:16 -04:00
func init() {
// descriptions are written in markdown for docs
schema.DescriptionKind = schema.StringMarkdown
}
2020-09-15 19:56:43 -04:00
func New() *schema.Provider {
2020-05-29 09:13:31 -07:00
p := &schema.Provider{
Schema: map[string]*schema.Schema{
2020-09-15 19:56:43 -04:00
"addr": {
Type: schema.TypeString,
Required: true,
2021-01-14 14:07:36 -08:00
Description: `The base url of the Boundary API, e.g. "http://127.0.0.1:9200". If not set, it will be read from the "BOUNDARY_ADDR" env var.`,
2020-09-15 19:56:43 -04:00
},
"token": {
2020-05-29 09:13:31 -07:00
Type: schema.TypeString,
Optional: true,
2020-09-15 19:56:43 -04:00
Description: `The Boundary token to use, as a string or path on disk containing just the string. If set, the token read here will be used in place of authenticating with the auth method specified in "auth_method_id", although the recovery KMS mechanism will still override this. Can also be set with the BOUNDARY_TOKEN environment variable.`,
2020-05-29 09:13:31 -07:00
},
2020-09-15 19:56:43 -04:00
"recovery_kms_hcl": {
2020-05-29 09:13:31 -07:00
Type: schema.TypeString,
2020-09-15 19:56:43 -04:00
Optional: true,
2020-10-13 11:02:52 -07:00
Description: "Can be a heredoc string or a path on disk. If set, the string/file will be parsed as HCL and used with the recovery KMS mechanism. While this is set, it will override any other authentication information; the KMS mechanism will always be used. See Boundary's KMS docs for examples: https://boundaryproject.io/docs/configuration/kms",
2020-05-29 09:13:31 -07:00
},
2020-07-12 15:09:40 -07:00
"auth_method_id": {
Type: schema.TypeString,
2020-09-15 19:56:43 -04:00
Optional: true,
Description: "The auth method ID e.g. ampw_1234567890. If not set, the default auth method for the given scope ID will be used.",
2020-07-12 15:09:40 -07:00
},
2020-09-15 19:56:43 -04:00
"password_auth_method_login_name": {
2020-07-12 15:09:40 -07:00
Type: schema.TypeString,
2020-09-15 19:56:43 -04:00
Optional: true,
Description: "The auth method login name for password-style auth methods",
Deprecated: "Use auth_method_login_name instead",
2020-07-12 15:09:40 -07:00
},
2020-09-15 19:56:43 -04:00
"password_auth_method_password": {
2020-07-12 15:09:40 -07:00
Type: schema.TypeString,
2020-09-15 19:56:43 -04:00
Optional: true,
Description: "The auth method password for password-style auth methods",
Deprecated: "Use auth_method_password instead",
},
"auth_method_login_name": {
Type: schema.TypeString,
Optional: true,
Description: "The auth method login name for password-style or ldap-style auth methods",
},
"auth_method_password": {
Type: schema.TypeString,
Optional: true,
Description: "The auth method password for password-style or ldap-style auth methods",
2020-07-12 15:09:40 -07:00
},
"tls_insecure": {
Type: schema.TypeBool,
Optional: true,
Description: "When set to true, does not validate the Boundary API endpoint certificate",
},
2022-10-03 17:03:14 -07:00
"plugin_execution_dir": {
Type: schema.TypeString,
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.`,
},
2020-05-14 17:06:23 -07:00
},
ResourcesMap: map[string]*schema.Resource{
"boundary_account": resourceAccount(),
"boundary_account_password": resourceAccountPassword(),
"boundary_account_oidc": resourceAccountOidc(),
"boundary_account_ldap": resourceAccountLdap(),
"boundary_auth_method": resourceAuthMethod(),
"boundary_auth_method_password": resourceAuthMethodPassword(),
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
"boundary_auth_method_ldap": resourceAuthMethodLdap(),
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
"boundary_credential_library_vault_ssh_certificate": resourceCredentialLibraryVaultSshCertificate(),
"boundary_credential_store_vault": resourceCredentialStoreVault(),
"boundary_credential_store_static": resourceCredentialStoreStatic(),
"boundary_credential_username_password": resourceCredentialUsernamePassword(),
"boundary_credential_ssh_private_key": resourceCredentialSshPrivateKey(),
"boundary_credential_json": resourceCredentialJson(),
"boundary_managed_group": resourceManagedGroup(),
"boundary_managed_group_ldap": resourceManagedGroupLdap(),
"boundary_group": resourceGroup(),
"boundary_host": resourceHost(),
"boundary_host_static": resourceHostStatic(),
"boundary_host_catalog": resourceHostCatalog(),
"boundary_host_catalog_static": resourceHostCatalogStatic(),
"boundary_host_catalog_plugin": resourceHostCatalogPlugin(),
"boundary_host_set": resourceHostSet(),
"boundary_host_set_static": resourceHostSetStatic(),
"boundary_host_set_plugin": resourceHostSetPlugin(),
"boundary_role": resourceRole(),
"boundary_scope": resourceScope(),
2023-06-28 10:38:22 -04:00
"boundary_storage_bucket": resourceStorageBucket(),
"boundary_target": resourceTarget(),
"boundary_user": resourceUser(),
"boundary_worker": resourceWorker(),
2020-05-14 17:06:23 -07:00
},
DataSourcesMap: map[string]*schema.Resource{
"boundary_scope": dataSourceScope(),
2023-10-17 19:36:51 -04:00
"boundary_user": dataSourceUser(),
},
2020-05-14 17:06:23 -07:00
}
2020-05-29 09:13:31 -07:00
2020-09-15 19:56:43 -04:00
p.ConfigureContextFunc = providerConfigure(p)
2020-05-29 09:13:31 -07:00
return p
}
type metaData struct {
2020-09-15 19:56:43 -04:00
client *api.Client
recoveryKmsWrapper wrapping.Wrapper
2020-05-29 09:13:31 -07:00
}
2020-09-15 19:56:43 -04:00
func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaData) error {
var credentials map[string]interface{}
amClient := authmethods.NewClient(md.client)
var providerScope string
scopeId, scopeIdOk := d.GetOk("scope_id")
switch {
case scopeIdOk:
providerScope = scopeId.(string)
default:
providerScope = DEFAULT_PROVIDER_SCOPE
}
2020-07-12 15:09:40 -07:00
2020-09-15 19:56:43 -04:00
recoveryKmsHcl, recoveryKmsHclOk := d.GetOk("recovery_kms_hcl")
if token, ok := d.GetOk("token"); ok {
md.client.SetToken(token.(string))
2020-07-12 15:09:40 -07:00
}
// If auth_method_id is not set, get the default auth method ID for the given scope ID.
// Skip fetching default auth method when recovery_kms_hcl is set or the client token isn't null.
authMethodId, authMethodIdOk := d.GetOk("auth_method_id")
if !authMethodIdOk && !recoveryKmsHclOk && md.client.Token() == "" {
defaultAuthMethodId, err := getDefaultAuthMethodId(ctx, amClient, providerScope, "")
if err != nil {
return err
}
authMethodIdOk = true
authMethodId = defaultAuthMethodId
}
2020-09-15 19:56:43 -04:00
switch {
case recoveryKmsHclOk:
recoveryHclStr, _, err := ReadPathOrContents(recoveryKmsHcl.(string))
if err != nil {
return fmt.Errorf(`error reading data from "recovery_kms_hcl": %v`, err)
}
2022-10-03 17:03:14 -07:00
opts := []pluginutil.Option{
pluginutil.WithPluginsMap(kms_plugin_assets.BuiltinKmsPlugins()),
pluginutil.WithPluginsFilesystem(kms_plugin_assets.KmsPluginPrefix, kms_plugin_assets.FileSystem()),
}
if execDir, ok := d.GetOk("plugin_execution_dir"); ok {
opts = append(opts, pluginutil.WithPluginExecutionDirectory(execDir.(string)))
}
2022-10-12 10:53:08 -07:00
// TODO: cleanup plugin when finished
wrapper, _, err := wrapper.GetWrapperFromHcl(
2022-10-03 17:03:14 -07:00
ctx,
recoveryHclStr,
"recovery",
configutil.WithPluginOptions(opts...))
2020-09-15 19:56:43 -04:00
if err != nil {
return fmt.Errorf(`error reading wrappers from "recovery_kms_hcl": %v`, err)
}
if wrapper == nil {
return errors.New(`No "kms" block with purpose "recovery" found in "recovery_kms_hcl"`)
}
md.recoveryKmsWrapper = wrapper
md.client.SetRecoveryKmsWrapper(wrapper)
return nil
case md.client.Token() != "":
// Use the token sourced from the conf file or env var
case authMethodIdOk:
switch {
case strings.HasPrefix(authMethodId.(string), PASSWORD_AUTH_METHOD_PREFIX) || strings.HasPrefix(authMethodId.(string), LDAP_AUTH_METHOD_PREFIX):
// Password-style & LDAP-style
authMethodLoginName, ok := d.GetOk("auth_method_login_name")
2020-09-15 19:56:43 -04:00
if !ok {
authMethodLoginName, ok = d.GetOk("password_auth_method_login_name")
if !ok {
return errors.New("auth method login name not set, please set auth_method_login_name on the provider")
}
2020-09-15 19:56:43 -04:00
}
authMethodPassword, ok := d.GetOk("auth_method_password")
2020-09-15 19:56:43 -04:00
if !ok {
authMethodPassword, ok = d.GetOk("password_auth_method_password")
if !ok {
return errors.New("auth method password not set, please set auth_method_password on the provider")
}
2020-09-15 19:56:43 -04:00
}
credentials = map[string]interface{}{
"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")
2020-09-15 19:56:43 -04:00
default:
return errors.New("no suitable typed auth method information found")
}
2020-07-12 15:09:40 -07:00
at, err := amClient.Authenticate(ctx, authMethodId.(string), "login", credentials)
2020-09-15 19:56:43 -04:00
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil {
statusCode := apiErr.Response().StatusCode()
if statusCode == http.StatusNotFound {
return fmt.Errorf("unknown auth_method_id: %s", err.Error())
}
if statusCode == http.StatusUnauthorized {
return fmt.Errorf("invalid login name or password: %s", err.Error())
}
}
2020-09-15 19:56:43 -04:00
return err
}
md.client.SetToken(at.Attributes["token"].(string))
2020-09-15 19:56:43 -04:00
default:
return errors.New("no suitable auth method information found")
2020-07-12 15:09:40 -07:00
}
2020-09-15 19:56:43 -04:00
return nil
2020-07-12 15:09:40 -07:00
}
2020-09-15 19:56:43 -04:00
func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
2020-05-29 09:13:31 -07:00
client, err := api.NewClient(nil)
if err != nil {
2020-09-15 19:56:43 -04:00
return nil, diag.FromErr(err)
2020-05-29 09:13:31 -07:00
}
2020-09-15 19:56:43 -04:00
if url, ok := d.GetOk("addr"); ok {
if err := client.SetAddr(url.(string)); err != nil {
return nil, diag.FromErr(err)
}
2020-09-15 19:56:43 -04:00
}
if client.Addr() == "" {
return nil, diag.Errorf(`"no valid address could be determined from "addr" or "BOUNDARY_ADDR" env var`)
2020-05-29 09:13:31 -07:00
}
if tlsInsecure, ok := d.GetOk("tls_insecure"); ok {
if client.SetTLSConfig(&api.TLSConfig{Insecure: tlsInsecure.(bool)}) != nil {
return nil, diag.Errorf("could not set insecure tls")
}
}
2020-05-29 09:13:31 -07:00
client.SetLimiter(5, 5)
2020-09-15 19:56:43 -04:00
md := &metaData{
client: client,
2020-07-12 15:09:40 -07:00
}
2020-09-15 19:56:43 -04:00
if err := providerAuthenticate(ctx, d, md); err != nil {
return nil, diag.FromErr(err)
}
return md, nil
2020-05-29 09:13:31 -07:00
}
2020-05-14 17:06:23 -07:00
}
// 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 matches the 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)
}