Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for setting client ID / secret through files #21764

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/provider-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ jobs:
- name: Set OIDC Token File Path
run: echo "${ARM_OIDC_TOKEN}" >"${RUNNER_TEMP}/oidc-token.jwt" && echo "ARM_OIDC_TOKEN_FILE_PATH=${RUNNER_TEMP}/oidc-token.jwt" >>${GITHUB_ENV}

- name: Set Client ID Path
run: echo "${{ secrets.ARM_CLIENT_ID }}" >"${RUNNER_TEMP}/client-id" && echo "ARM_CLIENT_ID_PATH=${RUNNER_TEMP}/client-id" >>${GITHUB_ENV}

- name: Set Client Secret Path
run: echo "${{ secrets.ARM_CLIENT_SECRET }}" >"${RUNNER_TEMP}/client-secret" && echo "ARM_CLIENT_SECRET_PATH=${RUNNER_TEMP}/client-secret" >>${GITHUB_ENV}

- name: Run provider tests
run: make testacc TEST=./internal/provider TESTARGS="-run '^TestAcc'"
env:
Expand All @@ -67,6 +73,14 @@ jobs:
run: rm -f "${RUNNER_TEMP}/oidc-token.jwt"
if: always()

- name: Clean Up Client ID Path
run: rm -f "${RUNNER_TEMP}/client-id"
if: always()

- name: Clean Up Client Secret Path
run: rm -f "${RUNNER_TEMP}/client-secret"
if: always()

- name: Add waiting-response on fail
if: failure()
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
Expand Down
72 changes: 70 additions & 2 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider {
Description: "The Client ID which should be used.",
},

"client_id_file_path": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID_FILE_PATH", ""),
Description: "The path to a file containing the Client ID which should be used.",
},

"tenant_id": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -238,6 +245,13 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider {
Description: "The Client Secret which should be used. For use When authenticating as a Service Principal using a Client Secret.",
},

"client_secret_file_path": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET_FILE_PATH", ""),
Description: "The path to a file containing the Client Secret which should be used. For use When authenticating as a Service Principal using a Client Secret.",
},

// OIDC specifc fields
"oidc_request_token": {
Type: schema.TypeString,
Expand Down Expand Up @@ -373,6 +387,16 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
return nil, diag.FromErr(err)
}

clientSecret, err := getClientSecret(d)
if err != nil {
return nil, diag.FromErr(err)
}

clientId, err := getClientId(d)
if err != nil {
return nil, diag.FromErr(err)
}

var (
env *environments.Environment

Expand All @@ -396,14 +420,14 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {

authConfig := &auth.Credentials{
Environment: *env,
ClientID: d.Get("client_id").(string),
ClientID: *clientId,
TenantID: d.Get("tenant_id").(string),
AuxiliaryTenantIDs: auxTenants,

ClientCertificateData: clientCertificateData,
ClientCertificatePath: d.Get("client_certificate_path").(string),
ClientCertificatePassword: d.Get("client_certificate_password").(string),
ClientSecret: d.Get("client_secret").(string),
ClientSecret: *clientSecret,

OIDCAssertionToken: *oidcToken,
GitHubOIDCTokenRequestURL: d.Get("oidc_request_url").(string),
Expand Down Expand Up @@ -505,6 +529,50 @@ func getOidcToken(d *schema.ResourceData) (*string, error) {
return &idToken, nil
}

func getClientId(d *schema.ResourceData) (*string, error) {
clientId := strings.TrimSpace(d.Get("client_id").(string))

if path := d.Get("client_id_file_path").(string); path != "" {
fileClientIdRaw, err := os.ReadFile(path)

if err != nil {
return nil, fmt.Errorf("reading Client ID from file %q: %v", path, err)
}

fileClientId := strings.TrimSpace(string(fileClientIdRaw))

if clientId != "" && clientId != fileClientId {
return nil, fmt.Errorf("mismatch between supplied Client ID and supplied Client ID file contents - please either remove one or ensure they match")
}

clientId = fileClientId
}

return &clientId, nil
}

func getClientSecret(d *schema.ResourceData) (*string, error) {
clientSecret := strings.TrimSpace(d.Get("client_secret").(string))

if path := d.Get("client_secret_file_path").(string); path != "" {
fileSecretRaw, err := os.ReadFile(path)

if err != nil {
return nil, fmt.Errorf("reading Client Secret from file %q: %v", path, err)
}

fileSecret := strings.TrimSpace(string(fileSecretRaw))

if clientSecret != "" && clientSecret != fileSecret {
return nil, fmt.Errorf("mismatch between supplied Client Secret and supplied Client Secret file contents - please either remove one or ensure they match")
}

clientSecret = fileSecret
}

return &clientSecret, nil
}

const resourceProviderRegistrationErrorFmt = `Error ensuring Resource Providers are registered.

Terraform automatically attempts to register the Resource Providers it supports to
Expand Down
87 changes: 85 additions & 2 deletions internal/provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ func TestAccProvider_clientCertificateAuth(t *testing.T) {
}

func TestAccProvider_clientSecretAuth(t *testing.T) {
t.Run("fromEnvironment", testAccProvider_clientSecretAuthFromEnvironment)
t.Run("fromFiles", testAccProvider_clientSecretAuthFromFiles)
}

func testAccProvider_clientSecretAuthFromEnvironment(t *testing.T) {
if os.Getenv("TF_ACC") == "" {
t.Skip("TF_ACC not set")
}
Expand All @@ -230,6 +235,11 @@ func TestAccProvider_clientSecretAuth(t *testing.T) {
t.Skip("ARM_CLIENT_SECRET not set")
}

// Ensure we are running using the expected env-vars
// t.SetEnv does automatic cleanup / resets the values after the test
t.Setenv("ARM_CLIENT_ID_FILE_PATH", "")
t.Setenv("ARM_CLIENT_SECRET_FILE_PATH", "")

logging.SetOutput(t)

provider := TestAzureProvider()
Expand All @@ -244,12 +254,85 @@ func TestAccProvider_clientSecretAuth(t *testing.T) {
t.Fatalf("configuring environment %q: %v", envName, err)
}

clientId, err := getClientId(d)
if err != nil {
return nil, diag.FromErr(err)
}

clientSecret, err := getClientSecret(d)
if err != nil {
return nil, diag.FromErr(err)
}

authConfig := &auth.Credentials{
Environment: *env,
TenantID: d.Get("tenant_id").(string),
ClientID: *clientId,
EnableAuthenticatingUsingClientSecret: true,
ClientSecret: *clientSecret,
}

return buildClient(ctx, provider, d, authConfig)
}

d := provider.Configure(ctx, terraform.NewResourceConfigRaw(nil))
if d != nil && d.HasError() {
t.Fatalf("err: %+v", d)
}

if errs := testCheckProvider(provider); len(errs) > 0 {
for _, err := range errs {
t.Error(err)
}
}
}

func testAccProvider_clientSecretAuthFromFiles(t *testing.T) {
if os.Getenv("TF_ACC") == "" {
t.Skip("TF_ACC not set")
}
if os.Getenv("ARM_CLIENT_ID_FILE_PATH") == "" {
t.Skip("ARM_CLIENT_ID_FILE_PATH not set")
}
if os.Getenv("ARM_CLIENT_SECRET_FILE_PATH") == "" {
t.Skip("ARM_CLIENT_SECRET_FILE_PATH not set")
}

// Ensure we are running using the expected env-vars
// t.SetEnv does automatic cleanup / resets the values after the test
t.Setenv("ARM_CLIENT_ID", "")
t.Setenv("ARM_CLIENT_SECRET", "")

logging.SetOutput(t)

provider := TestAzureProvider()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

// Support only Client Certificate authentication
provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
envName := d.Get("environment").(string)
env, err := environments.FromName(envName)
if err != nil {
t.Fatalf("configuring environment %q: %v", envName, err)
}

clientId, err := getClientId(d)
if err != nil {
return nil, diag.FromErr(err)
}

clientSecret, err := getClientSecret(d)
if err != nil {
return nil, diag.FromErr(err)
}

authConfig := &auth.Credentials{
Environment: *env,
TenantID: d.Get("tenant_id").(string),
ClientID: d.Get("client_id").(string),
ClientID: *clientId,
EnableAuthenticatingUsingClientSecret: true,
ClientSecret: d.Get("client_secret").(string),
ClientSecret: *clientSecret,
}

return buildClient(ctx, provider, d, authConfig)
Expand Down