cli: use uami for in-cluter authentication (#1820)

This commit is contained in:
3u13r 2023-05-26 11:45:03 +02:00 committed by GitHub
parent 9502bc8ff4
commit 661f084ffa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 65 additions and 117 deletions

View file

@ -201,8 +201,6 @@ func (c *IAMCreator) createAzure(ctx context.Context, cl terraformClient, opts *
return iamid.File{ return iamid.File{
CloudProvider: cloudprovider.Azure, CloudProvider: cloudprovider.Azure,
AzureOutput: iamid.AzureFile{ AzureOutput: iamid.AzureFile{
ApplicationID: iamOutput.Azure.ApplicationID,
ApplicationClientSecretValue: iamOutput.Azure.ApplicationClientSecretValue,
SubscriptionID: iamOutput.Azure.SubscriptionID, SubscriptionID: iamOutput.Azure.SubscriptionID,
TenantID: iamOutput.Azure.TenantID, TenantID: iamOutput.Azure.TenantID,
UAMIID: iamOutput.Azure.UAMIID, UAMIID: iamOutput.Azure.UAMIID,

View file

@ -52,8 +52,6 @@ func TestIAMCreator(t *testing.T) {
Azure: terraform.AzureIAMOutput{ Azure: terraform.AzureIAMOutput{
SubscriptionID: "test_subscription_id", SubscriptionID: "test_subscription_id",
TenantID: "test_tenant_id", TenantID: "test_tenant_id",
ApplicationID: "test_application_id",
ApplicationClientSecretValue: "test_application_client_secret_value",
UAMIID: "test_uami_id", UAMIID: "test_uami_id",
}, },
} }
@ -62,8 +60,6 @@ func TestIAMCreator(t *testing.T) {
AzureOutput: iamid.AzureFile{ AzureOutput: iamid.AzureFile{
SubscriptionID: "test_subscription_id", SubscriptionID: "test_subscription_id",
TenantID: "test_tenant_id", TenantID: "test_tenant_id",
ApplicationID: "test_application_id",
ApplicationClientSecretValue: "test_application_client_secret_value",
UAMIID: "test_uami_id", UAMIID: "test_uami_id",
}, },
} }

View file

@ -486,8 +486,6 @@ func (c *azureIAMCreator) printOutputValues(cmd *cobra.Command, flags iamFlags,
cmd.Printf("location:\t\t%s\n", flags.azure.region) cmd.Printf("location:\t\t%s\n", flags.azure.region)
cmd.Printf("resourceGroup:\t\t%s\n", flags.azure.resourceGroup) cmd.Printf("resourceGroup:\t\t%s\n", flags.azure.resourceGroup)
cmd.Printf("userAssignedIdentity:\t%s\n", iamFile.AzureOutput.UAMIID) cmd.Printf("userAssignedIdentity:\t%s\n", iamFile.AzureOutput.UAMIID)
cmd.Printf("appClientID:\t\t%s\n", iamFile.AzureOutput.ApplicationID)
cmd.Printf("clientSecretValue:\t%s\n\n", iamFile.AzureOutput.ApplicationClientSecretValue)
} }
func (c *azureIAMCreator) writeOutputValuesToConfig(conf *config.Config, flags iamFlags, iamFile iamid.File) { func (c *azureIAMCreator) writeOutputValuesToConfig(conf *config.Config, flags iamFlags, iamFile iamid.File) {
@ -496,8 +494,6 @@ func (c *azureIAMCreator) writeOutputValuesToConfig(conf *config.Config, flags i
conf.Provider.Azure.Location = flags.azure.region conf.Provider.Azure.Location = flags.azure.region
conf.Provider.Azure.ResourceGroup = flags.azure.resourceGroup conf.Provider.Azure.ResourceGroup = flags.azure.resourceGroup
conf.Provider.Azure.UserAssignedIdentity = iamFile.AzureOutput.UAMIID conf.Provider.Azure.UserAssignedIdentity = iamFile.AzureOutput.UAMIID
conf.Provider.Azure.AppClientID = iamFile.AzureOutput.ApplicationID
conf.Provider.Azure.ClientSecretValue = iamFile.AzureOutput.ApplicationClientSecretValue
} }
func (c *azureIAMCreator) parseAndWriteIDFile(_ iamid.File, _ file.Handler) error { func (c *azureIAMCreator) parseAndWriteIDFile(_ iamid.File, _ file.Handler) error {

View file

@ -360,8 +360,6 @@ func TestIAMCreateAzure(t *testing.T) {
AzureOutput: iamid.AzureFile{ AzureOutput: iamid.AzureFile{
SubscriptionID: "test_subscription_id", SubscriptionID: "test_subscription_id",
TenantID: "test_tenant_id", TenantID: "test_tenant_id",
ApplicationID: "test_application_id",
ApplicationClientSecretValue: "test_application_client_secret_value",
UAMIID: "test_uami_id", UAMIID: "test_uami_id",
}, },
} }
@ -611,8 +609,6 @@ func TestIAMCreateAzure(t *testing.T) {
require.NoError(readErr) require.NoError(readErr)
assert.Equal(tc.creator.id.AzureOutput.SubscriptionID, readConfig.Provider.Azure.SubscriptionID) assert.Equal(tc.creator.id.AzureOutput.SubscriptionID, readConfig.Provider.Azure.SubscriptionID)
assert.Equal(tc.creator.id.AzureOutput.TenantID, readConfig.Provider.Azure.TenantID) assert.Equal(tc.creator.id.AzureOutput.TenantID, readConfig.Provider.Azure.TenantID)
assert.Equal(tc.creator.id.AzureOutput.ApplicationID, readConfig.Provider.Azure.AppClientID)
assert.Equal(tc.creator.id.AzureOutput.ApplicationClientSecretValue, readConfig.Provider.Azure.ClientSecretValue)
assert.Equal(tc.creator.id.AzureOutput.UAMIID, readConfig.Provider.Azure.UserAssignedIdentity) assert.Equal(tc.creator.id.AzureOutput.UAMIID, readConfig.Provider.Azure.UserAssignedIdentity)
assert.Equal(tc.regionFlag, readConfig.Provider.Azure.Location) assert.Equal(tc.regionFlag, readConfig.Provider.Azure.Location)
assert.Equal(tc.resourceGroupFlag, readConfig.Provider.Azure.ResourceGroup) assert.Equal(tc.resourceGroupFlag, readConfig.Provider.Azure.ResourceGroup)

View file

@ -449,13 +449,19 @@ func (i *initCmd) getMarshaledServiceAccountURI(provider cloudprovider.Provider,
return "", nil // AWS does not need a service account URI return "", nil // AWS does not need a service account URI
case cloudprovider.Azure: case cloudprovider.Azure:
i.log.Debugf("Handling case for Azure") i.log.Debugf("Handling case for Azure")
// TODO(3u13r): Remove this fallback and enforce assigned managed identity after the v2.8.0 but before the v2.9.0 release.
authMethod := azureshared.AuthMethodUserAssignedIdentity
if config.Provider.Azure.AppClientID != "" {
authMethod = azureshared.AuthMethodServicePrincipal
}
creds := azureshared.ApplicationCredentials{ creds := azureshared.ApplicationCredentials{
TenantID: config.Provider.Azure.TenantID, TenantID: config.Provider.Azure.TenantID,
AppClientID: config.Provider.Azure.AppClientID, AppClientID: config.Provider.Azure.AppClientID,
ClientSecretValue: config.Provider.Azure.ClientSecretValue, ClientSecretValue: config.Provider.Azure.ClientSecretValue,
Location: config.Provider.Azure.Location, Location: config.Provider.Azure.Location,
// TODO(malt3): Switch preferred auth method to uami as planned by AB#2961 PreferredAuthMethod: authMethod,
PreferredAuthMethod: azureshared.AuthMethodServicePrincipal,
UamiResourceID: config.Provider.Azure.UserAssignedIdentity, UamiResourceID: config.Provider.Azure.UserAssignedIdentity,
} }
return creds.ToCloudServiceAccountURI(), nil return creds.ToCloudServiceAccountURI(), nil

View file

@ -33,9 +33,7 @@ type GCPFile struct {
type AzureFile struct { type AzureFile struct {
SubscriptionID string `json:"subscriptionID,omitempty"` SubscriptionID string `json:"subscriptionID,omitempty"`
TenantID string `json:"tenantID,omitempty"` TenantID string `json:"tenantID,omitempty"`
ApplicationID string `json:"applicationID,omitempty"`
UAMIID string `json:"uamiID,omitempty"` UAMIID string `json:"uamiID,omitempty"`
ApplicationClientSecretValue string `json:"applicationClientSecretValue,omitempty"`
} }
// AWSFile contains the output information of an AWS IAM configuration. // AWSFile contains the output information of an AWS IAM configuration.

View file

@ -187,9 +187,7 @@ type GCPIAMOutput struct {
type AzureIAMOutput struct { type AzureIAMOutput struct {
SubscriptionID string SubscriptionID string
TenantID string TenantID string
ApplicationID string
UAMIID string UAMIID string
ApplicationClientSecretValue string
} }
// AWSIAMOutput contains the output information of the Terraform IAM operation on GCP. // AWSIAMOutput contains the output information of the Terraform IAM operation on GCP.
@ -249,14 +247,6 @@ func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Pro
if !ok { if !ok {
return IAMOutput{}, errors.New("invalid type in tenant id output: not a string") return IAMOutput{}, errors.New("invalid type in tenant id output: not a string")
} }
applicationIDRaw, ok := tfState.Values.Outputs["application_id"]
if !ok {
return IAMOutput{}, errors.New("no application id output found")
}
applicationIDOutput, ok := applicationIDRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in application id output: not a string")
}
uamiIDRaw, ok := tfState.Values.Outputs["uami_id"] uamiIDRaw, ok := tfState.Values.Outputs["uami_id"]
if !ok { if !ok {
return IAMOutput{}, errors.New("no UAMI id output found") return IAMOutput{}, errors.New("no UAMI id output found")
@ -265,21 +255,11 @@ func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Pro
if !ok { if !ok {
return IAMOutput{}, errors.New("invalid type in UAMI id output: not a string") return IAMOutput{}, errors.New("invalid type in UAMI id output: not a string")
} }
appClientSecretRaw, ok := tfState.Values.Outputs["application_client_secret_value"]
if !ok {
return IAMOutput{}, errors.New("no application client secret value output found")
}
appClientSecretOutput, ok := appClientSecretRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in application client secret valueoutput: not a string")
}
return IAMOutput{ return IAMOutput{
Azure: AzureIAMOutput{ Azure: AzureIAMOutput{
SubscriptionID: subscriptionIDOutput, SubscriptionID: subscriptionIDOutput,
TenantID: tenantIDOutput, TenantID: tenantIDOutput,
ApplicationID: applicationIDOutput,
UAMIID: uamiIDOutput, UAMIID: uamiIDOutput,
ApplicationClientSecretValue: appClientSecretOutput,
}, },
}, nil }, nil
case cloudprovider.AWS: case cloudprovider.AWS:

View file

@ -68,31 +68,3 @@ resource "azurerm_role_assignment" "uami_owner_role" {
role_definition_name = "Owner" role_definition_name = "Owner"
principal_id = azurerm_user_assigned_identity.identity_uami.principal_id principal_id = azurerm_user_assigned_identity.identity_uami.principal_id
} }
# the app registration, application secrets
# and role assignments below will be removed in the future
# TODO(malt3): remove app registration as planned by AB#2961
# Create application registration
resource "azuread_application" "base_application" {
display_name = "${var.resource_group_name}-application"
owners = [data.azuread_client_config.current.object_id]
}
resource "azuread_service_principal" "application_principal" {
application_id = azuread_application.base_application.application_id
app_role_assignment_required = false
owners = [data.azuread_client_config.current.object_id]
}
# Set identity as base resource group owner
resource "azurerm_role_assignment" "app_registration_owner_role" {
scope = azurerm_resource_group.base_resource_group.id
role_definition_name = "Owner"
principal_id = azuread_service_principal.application_principal.object_id
}
# Create application secret (password)
resource "azuread_application_password" "base_application_secret" {
application_object_id = azuread_application.base_application.object_id
}

View file

@ -6,15 +6,6 @@ output "tenant_id" {
value = data.azurerm_subscription.current.tenant_id value = data.azurerm_subscription.current.tenant_id
} }
output "application_id" {
value = azuread_application.base_application.application_id
}
output "uami_id" { output "uami_id" {
value = azurerm_user_assigned_identity.identity_uami.id value = azurerm_user_assigned_identity.identity_uami.id
} }
output "application_client_secret_value" {
value = azuread_application_password.base_application_secret.value
sensitive = true
}

View file

@ -117,7 +117,6 @@ func TestPrepareIAM(t *testing.T) {
} }
azureVars := &AzureIAMVariables{ azureVars := &AzureIAMVariables{
Region: "westus", Region: "westus",
ServicePrincipal: "constell-test-sp",
ResourceGroup: "constell-test-rg", ResourceGroup: "constell-test-rg",
} }
awsVars := &AWSIAMVariables{ awsVars := &AWSIAMVariables{
@ -481,7 +480,6 @@ func TestCreateIAM(t *testing.T) {
} }
azureVars := &AzureIAMVariables{ azureVars := &AzureIAMVariables{
Region: "westus", Region: "westus",
ServicePrincipal: "constell-test-sp",
ResourceGroup: "constell-test-rg", ResourceGroup: "constell-test-rg",
} }
awsVars := &AWSIAMVariables{ awsVars := &AWSIAMVariables{
@ -583,8 +581,6 @@ func TestCreateIAM(t *testing.T) {
want: IAMOutput{Azure: AzureIAMOutput{ want: IAMOutput{Azure: AzureIAMOutput{
SubscriptionID: "test_subscription_id", SubscriptionID: "test_subscription_id",
TenantID: "test_tenant_id", TenantID: "test_tenant_id",
ApplicationID: "test_application_id",
ApplicationClientSecretValue: "test_application_client_secret_value",
UAMIID: "test_uami_id", UAMIID: "test_uami_id",
}}, }},
}, },

View file

@ -1,8 +1,19 @@
# Configuration migrations # Migrations
This document describes breaking changes in the configuration file format between Constellation releases. This document describes breaking changes and migrations between Constellation releases.
Use [`constellation config migrate`](./cli.md#constellation-config-migrate) to automatically update an old config file to a new format. Use [`constellation config migrate`](./cli.md#constellation-config-migrate) to automatically update an old config file to a new format.
## Migrating from Azure's service principal authentication to managed identity authentication
- The `provider.azure.appClientID` and `provider.azure.appClientSecret` fields are no longer required and should be removed.
- To keep using an existing UAMI add the `Owner` permission with the scope of your `resourceGroup`.
- Otherwise, simply [create new Constellation IAM credentials](../workflows/config.md#creating-iam-credentials) and use the created UAMI.
- To migrate the authentication for an existing Constellation on Azure to an UAMI with the necessary permissions:
1. Remove the `aadClientId` and `aadClientSecret` from the azureconfig secret.
2. Set `useManagedIdentityExtension` to `true` and use the `userAssignedIdentity` from the Constellation config for the value of `userAssignedIdentityID`.
3. Restart the CSI driver, cloud controller manager, cluster autoscaler, and Constellation operator pods.
## Migrating from CLI versions before 2.8 ## Migrating from CLI versions before 2.8
- The `measurements` field for each cloud service provider was replaced with a global `attestation` field. - The `measurements` field for each cloud service provider was replaced with a global `attestation` field.

View file

@ -143,10 +143,10 @@ type AzureConfig struct {
UserAssignedIdentity string `yaml:"userAssignedIdentity" validate:"required"` UserAssignedIdentity string `yaml:"userAssignedIdentity" validate:"required"`
// description: | // description: |
// Application client ID of the Active Directory app registration. // Application client ID of the Active Directory app registration.
AppClientID string `yaml:"appClientID" validate:"uuid"` AppClientID string `yaml:"appClientID,omitempty" validate:"omitempty,uuid"`
// description: | // description: |
// Client secret value of the Active Directory app registration credentials. Alternatively leave empty and pass value via CONSTELL_AZURE_CLIENT_SECRET_VALUE environment variable. // Client secret value of the Active Directory app registration credentials. Alternatively leave empty and pass value via CONSTELL_AZURE_CLIENT_SECRET_VALUE environment variable.
ClientSecretValue string `yaml:"clientSecretValue" validate:"required"` ClientSecretValue string `yaml:"clientSecretValue,omitempty" validate:"omitempty"`
// description: | // description: |
// VM instance type to use for Constellation nodes. // VM instance type to use for Constellation nodes.
InstanceType string `yaml:"instanceType" validate:"azure_instance_type"` InstanceType string `yaml:"instanceType" validate:"azure_instance_type"`
@ -419,6 +419,14 @@ func NewWithClient(fileHandler file.Handler, name string, client fetcher.HTTPCli
c.MicroserviceVersion = Default().MicroserviceVersion c.MicroserviceVersion = Default().MicroserviceVersion
} }
// TODO(3u13r): Remove this deprecation warning and enforce assigned managed identity after the v2.8.0 but before the v2.9.0 release.
if c.Provider.Azure != nil &&
(c.Provider.Azure.AppClientID != "" || c.Provider.Azure.ClientSecretValue != "") {
// Deprecation warning for old auth method
fmt.Fprintf(os.Stderr, "WARNING: Using a service principal for authentication is deprecated and will be removed in an upcoming version.")
fmt.Fprintf(os.Stderr, " Migrate to using a user assigned managed identity by following the migration guide: https://docs.edgeless.systems/constellation/reference/migration.")
}
return c, c.Validate(force) return c, c.Validate(force)
} }

View file

@ -277,8 +277,8 @@ func TestNewWithDefaultOptions(t *testing.T) {
} }
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
const defaultErrCount = 34 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default const defaultErrCount = 32 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
const azErrCount = 9 const azErrCount = 7
const gcpErrCount = 6 const gcpErrCount = 6
// TODO(AB#3132,3u13r): refactor config validation tests // TODO(AB#3132,3u13r): refactor config validation tests