Skip to content

Commit

Permalink
Merge pull request #68 from grafana/andreas/user-auth-backend
Browse files Browse the repository at this point in the history
Support user-auth service principal
  • Loading branch information
aangelisc authored Feb 23, 2024
2 parents 74e691d + 8d08bb1 commit 3dc3da5
Show file tree
Hide file tree
Showing 9 changed files with 589 additions and 128 deletions.
2 changes: 2 additions & 0 deletions azcredentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type AzureCredentials interface {

// AadCurrentUserCredentials "Current User" user identity credentials of the current Grafana user.
type AadCurrentUserCredentials struct {
ServiceCredentialsEnabled bool
ServiceCredentials AzureCredentials
}

// AzureManagedIdentityCredentials "Managed Identity" service managed identity credentials configured
Expand Down
18 changes: 13 additions & 5 deletions azsettings/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ const (
WorkloadIdentityClientID = "GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID"
WorkloadIdentityTokenFile = "GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE"

UserIdentityEnabled = "GFAZPL_USER_IDENTITY_ENABLED"
UserIdentityTokenURL = "GFAZPL_USER_IDENTITY_TOKEN_URL"
UserIdentityClientID = "GFAZPL_USER_IDENTITY_CLIENT_ID"
UserIdentityClientSecret = "GFAZPL_USER_IDENTITY_CLIENT_SECRET"
UserIdentityAssertion = "GFAZPL_USER_IDENTITY_ASSERTION"
UserIdentityEnabled = "GFAZPL_USER_IDENTITY_ENABLED"
UserIdentityTokenURL = "GFAZPL_USER_IDENTITY_TOKEN_URL"
UserIdentityClientID = "GFAZPL_USER_IDENTITY_CLIENT_ID"
UserIdentityClientSecret = "GFAZPL_USER_IDENTITY_CLIENT_SECRET"
UserIdentityAssertion = "GFAZPL_USER_IDENTITY_ASSERTION"
UserIdentityFallbackCredentialsEnabled = "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED"

// Pre Grafana 9.x variables
fallbackAzureCloud = "AZURE_CLOUD"
Expand Down Expand Up @@ -89,13 +90,19 @@ func ReadFromEnv() (*AzureSettings, error) {
assertion := envutil.GetOrDefault(UserIdentityAssertion, "")
usernameAssertion := assertion == "username"

serviceCredentialsFallback, err := envutil.GetBoolOrDefault(UserIdentityFallbackCredentialsEnabled, true)
if err != nil {
return nil, err
}

azureSettings.UserIdentityEnabled = true
azureSettings.UserIdentityTokenEndpoint = &TokenEndpointSettings{
TokenUrl: tokenUrl,
ClientId: clientId,
ClientSecret: clientSecret,
UsernameAssertion: usernameAssertion,
}
azureSettings.UserIdentityFallbackCredentialsEnabled = serviceCredentialsFallback
}

return azureSettings, nil
Expand Down Expand Up @@ -139,6 +146,7 @@ func WriteToEnvStr(azureSettings *AzureSettings) []string {

if azureSettings.UserIdentityEnabled {
envs = append(envs, fmt.Sprintf("%s=true", UserIdentityEnabled))
envs = append(envs, fmt.Sprintf("%s=%t", UserIdentityFallbackCredentialsEnabled, azureSettings.UserIdentityFallbackCredentialsEnabled))

if tokenEndpoint := azureSettings.UserIdentityTokenEndpoint; tokenEndpoint != nil {
if tokenEndpoint.TokenUrl != "" {
Expand Down
64 changes: 59 additions & 5 deletions azsettings/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,45 @@ func TestReadFromEnv(t *testing.T) {
require.NotNil(t, azureSettings.UserIdentityTokenEndpoint)
assert.Equal(t, "87808761-ff7b-492e-bb0d-5de2437ffa55", azureSettings.UserIdentityTokenEndpoint.ClientSecret)
})

t.Run("should be enabled and default to enabling service credentials", func(t *testing.T) {
unset1, err := setEnvVar("GFAZPL_USER_IDENTITY_TOKEN_URL", "https://login.microsoftonline.com/fd719c11-a91c-40fd-8379-1e6cd3c59568/oauth2/v2.0/token")
require.NoError(t, err)
defer unset1()
unset2, err := setEnvVar("GFAZPL_USER_IDENTITY_CLIENT_ID", "f85aa887-490d-4fac-9306-9b99ad0aa31d")
require.NoError(t, err)
defer unset2()
unset3, err := setEnvVar("GFAZPL_USER_IDENTITY_CLIENT_SECRET", "87808761-ff7b-492e-bb0d-5de2437ffa55")
require.NoError(t, err)
defer unset3()
azureSettings, err := ReadFromEnv()
require.NoError(t, err)

assert.True(t, azureSettings.UserIdentityEnabled)
assert.True(t, azureSettings.UserIdentityFallbackCredentialsEnabled)

})

t.Run("should be enabled and disable service credentials", func(t *testing.T) {
unset1, err := setEnvVar("GFAZPL_USER_IDENTITY_TOKEN_URL", "https://login.microsoftonline.com/fd719c11-a91c-40fd-8379-1e6cd3c59568/oauth2/v2.0/token")
require.NoError(t, err)
defer unset1()
unset2, err := setEnvVar("GFAZPL_USER_IDENTITY_CLIENT_ID", "f85aa887-490d-4fac-9306-9b99ad0aa31d")
require.NoError(t, err)
defer unset2()
unset3, err := setEnvVar("GFAZPL_USER_IDENTITY_CLIENT_SECRET", "87808761-ff7b-492e-bb0d-5de2437ffa55")
require.NoError(t, err)
defer unset3()
unset4, err := setEnvVar("GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED", "false")
require.NoError(t, err)
defer unset4()
azureSettings, err := ReadFromEnv()
require.NoError(t, err)

assert.True(t, azureSettings.UserIdentityEnabled)
assert.False(t, azureSettings.UserIdentityFallbackCredentialsEnabled)

})
})

t.Run("when user identity disabled", func(t *testing.T) {
Expand Down Expand Up @@ -483,8 +522,22 @@ func TestWriteToEnvStr(t *testing.T) {

envs := WriteToEnvStr(azureSettings)

require.Len(t, envs, 1)
require.Len(t, envs, 2)
assert.Equal(t, "GFAZPL_USER_IDENTITY_ENABLED=true", envs[0])
assert.Equal(t, "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED=false", envs[1])
})

t.Run("should return user identity set if enabled with service credentials enabled", func(t *testing.T) {
azureSettings := &AzureSettings{
UserIdentityEnabled: true,
UserIdentityFallbackCredentialsEnabled: true,
}

envs := WriteToEnvStr(azureSettings)

require.Len(t, envs, 2)
assert.Equal(t, "GFAZPL_USER_IDENTITY_ENABLED=true", envs[0])
assert.Equal(t, "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED=true", envs[1])
})

t.Run("should return user identity endpoint settings if user identity enabled", func(t *testing.T) {
Expand All @@ -499,11 +552,12 @@ func TestWriteToEnvStr(t *testing.T) {

envs := WriteToEnvStr(azureSettings)

assert.Len(t, envs, 4)
assert.Len(t, envs, 5)
assert.Equal(t, "GFAZPL_USER_IDENTITY_ENABLED=true", envs[0])
assert.Equal(t, "GFAZPL_USER_IDENTITY_TOKEN_URL=https://login.microsoftonline.com/fd719c11-a91c-40fd-8379-1e6cd3c59568/oauth2/v2.0/token", envs[1])
assert.Equal(t, "GFAZPL_USER_IDENTITY_CLIENT_ID=f85aa887-490d-4fac-9306-9b99ad0aa31d", envs[2])
assert.Equal(t, "GFAZPL_USER_IDENTITY_CLIENT_SECRET=87808761-ff7b-492e-bb0d-5de2437ffa55", envs[3])
assert.Equal(t, "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED=false", envs[1])
assert.Equal(t, "GFAZPL_USER_IDENTITY_TOKEN_URL=https://login.microsoftonline.com/fd719c11-a91c-40fd-8379-1e6cd3c59568/oauth2/v2.0/token", envs[2])
assert.Equal(t, "GFAZPL_USER_IDENTITY_CLIENT_ID=f85aa887-490d-4fac-9306-9b99ad0aa31d", envs[3])
assert.Equal(t, "GFAZPL_USER_IDENTITY_CLIENT_SECRET=87808761-ff7b-492e-bb0d-5de2437ffa55", envs[4])
})

t.Run("should not return user identity endpoint settings if user identity not enabled", func(t *testing.T) {
Expand Down
9 changes: 7 additions & 2 deletions azsettings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ type AzureSettings struct {
WorkloadIdentityEnabled bool
WorkloadIdentitySettings *WorkloadIdentitySettings

UserIdentityEnabled bool
UserIdentityTokenEndpoint *TokenEndpointSettings
UserIdentityEnabled bool
UserIdentityTokenEndpoint *TokenEndpointSettings
UserIdentityFallbackCredentialsEnabled bool

// This field determines which plugins will receive the settings via plugin context
ForwardSettingsPlugins []string
Expand Down Expand Up @@ -79,6 +80,7 @@ func ReadFromContext(ctx context.Context) (*AzureSettings, bool) {
hasSettings = true

settings.UserIdentityTokenEndpoint = &TokenEndpointSettings{}
settings.UserIdentityFallbackCredentialsEnabled = true

if v := cfg.Get(UserIdentityClientID); v != "" {
settings.UserIdentityTokenEndpoint.ClientId = v
Expand All @@ -92,6 +94,9 @@ func ReadFromContext(ctx context.Context) (*AzureSettings, bool) {
if v := cfg.Get(UserIdentityAssertion); v == "username" {
settings.UserIdentityTokenEndpoint.UsernameAssertion = true
}
if v := cfg.Get(UserIdentityFallbackCredentialsEnabled); v == strconv.FormatBool(false) {
settings.UserIdentityFallbackCredentialsEnabled = false
}
}

if v := cfg.Get(WorkloadIdentityEnabled); v == strconv.FormatBool(true) {
Expand Down
83 changes: 45 additions & 38 deletions azsettings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,28 @@ func TestSettingsFromContext(t *testing.T) {
{
name: "azure settings in config",
cfg: backend.NewGrafanaCfg(map[string]string{
AzureCloud: AzurePublic,
AzureAuthEnabled: "true",
ManagedIdentityEnabled: "true",
ManagedIdentityClientID: "mock_managed_identity_client_id",
UserIdentityEnabled: "true",
UserIdentityClientID: "mock_user_identity_client_id",
UserIdentityClientSecret: "mock_managed_identity_client_secret",
UserIdentityTokenURL: "mock_managed_identity_token_url",
UserIdentityAssertion: "username",
WorkloadIdentityEnabled: "true",
WorkloadIdentityClientID: "mock_workload_identity_client_id",
WorkloadIdentityTenantID: "mock_workload_identity_tenant_id",
WorkloadIdentityTokenFile: "mock_workload_identity_token_file",
AzureCloud: AzurePublic,
AzureAuthEnabled: "true",
ManagedIdentityEnabled: "true",
ManagedIdentityClientID: "mock_managed_identity_client_id",
UserIdentityEnabled: "true",
UserIdentityClientID: "mock_user_identity_client_id",
UserIdentityClientSecret: "mock_managed_identity_client_secret",
UserIdentityTokenURL: "mock_managed_identity_token_url",
UserIdentityAssertion: "username",
UserIdentityFallbackCredentialsEnabled: "true",
WorkloadIdentityEnabled: "true",
WorkloadIdentityClientID: "mock_workload_identity_client_id",
WorkloadIdentityTenantID: "mock_workload_identity_tenant_id",
WorkloadIdentityTokenFile: "mock_workload_identity_token_file",
}),
expectedAzure: &AzureSettings{
Cloud: AzurePublic,
AzureAuthEnabled: true,
ManagedIdentityEnabled: true,
ManagedIdentityClientId: "mock_managed_identity_client_id",
UserIdentityEnabled: true,
Cloud: AzurePublic,
AzureAuthEnabled: true,
ManagedIdentityEnabled: true,
ManagedIdentityClientId: "mock_managed_identity_client_id",
UserIdentityEnabled: true,
UserIdentityFallbackCredentialsEnabled: true,
UserIdentityTokenEndpoint: &TokenEndpointSettings{
ClientId: "mock_user_identity_client_id",
ClientSecret: "mock_managed_identity_client_secret",
Expand Down Expand Up @@ -92,10 +94,11 @@ func TestSettingsFromContext(t *testing.T) {

func TestReadSettings(t *testing.T) {
expectedAzureContextSettings := &AzureSettings{
Cloud: AzurePublic,
ManagedIdentityEnabled: true,
ManagedIdentityClientId: "mock_managed_identity_client_id",
UserIdentityEnabled: true,
Cloud: AzurePublic,
ManagedIdentityEnabled: true,
ManagedIdentityClientId: "mock_managed_identity_client_id",
UserIdentityEnabled: true,
UserIdentityFallbackCredentialsEnabled: false,
UserIdentityTokenEndpoint: &TokenEndpointSettings{
ClientId: "mock_user_identity_client_id",
ClientSecret: "mock_managed_identity_client_secret",
Expand All @@ -111,10 +114,11 @@ func TestReadSettings(t *testing.T) {
}

expectedAzureEnvSettings := &AzureSettings{
Cloud: "ENV_CLOUD",
ManagedIdentityEnabled: true,
ManagedIdentityClientId: "ENV_MI_CLIENT_ID",
UserIdentityEnabled: true,
Cloud: "ENV_CLOUD",
ManagedIdentityEnabled: true,
ManagedIdentityClientId: "ENV_MI_CLIENT_ID",
UserIdentityEnabled: true,
UserIdentityFallbackCredentialsEnabled: false,
UserIdentityTokenEndpoint: &TokenEndpointSettings{
ClientId: "ENV_UI_CLIENT_ID",
ClientSecret: "ENV_UI_CLIENT_SECRET",
Expand Down Expand Up @@ -145,6 +149,8 @@ func TestReadSettings(t *testing.T) {
defer unsetUITokenURL()
unsetUIAssertion, _ := setEnvVar(UserIdentityAssertion, "username")
defer unsetUIAssertion()
unsetUIServiceCredentials, _ := setEnvVar(UserIdentityFallbackCredentialsEnabled, "false")
defer unsetUIServiceCredentials()
unsetWIEnabled, _ := setEnvVar(WorkloadIdentityEnabled, "true")
defer unsetWIEnabled()
unsetWIClientID, _ := setEnvVar(WorkloadIdentityClientID, "ENV_WI_CLIENT_ID")
Expand All @@ -165,18 +171,19 @@ func TestReadSettings(t *testing.T) {
{
name: "read from context",
cfg: backend.NewGrafanaCfg(map[string]string{
AzureCloud: AzurePublic,
ManagedIdentityEnabled: "true",
ManagedIdentityClientID: "mock_managed_identity_client_id",
UserIdentityEnabled: "true",
UserIdentityClientID: "mock_user_identity_client_id",
UserIdentityClientSecret: "mock_managed_identity_client_secret",
UserIdentityTokenURL: "mock_managed_identity_token_url",
UserIdentityAssertion: "username",
WorkloadIdentityEnabled: "true",
WorkloadIdentityClientID: "mock_workload_identity_client_id",
WorkloadIdentityTenantID: "mock_workload_identity_tenant_id",
WorkloadIdentityTokenFile: "mock_workload_identity_token_file",
AzureCloud: AzurePublic,
ManagedIdentityEnabled: "true",
ManagedIdentityClientID: "mock_managed_identity_client_id",
UserIdentityEnabled: "true",
UserIdentityClientID: "mock_user_identity_client_id",
UserIdentityClientSecret: "mock_managed_identity_client_secret",
UserIdentityTokenURL: "mock_managed_identity_token_url",
UserIdentityAssertion: "username",
UserIdentityFallbackCredentialsEnabled: "false",
WorkloadIdentityEnabled: "true",
WorkloadIdentityClientID: "mock_workload_identity_client_id",
WorkloadIdentityTenantID: "mock_workload_identity_tenant_id",
WorkloadIdentityTokenFile: "mock_workload_identity_token_file",
}),
expectedAzure: expectedAzureContextSettings,
expectedError: nil,
Expand Down
Loading

0 comments on commit 3dc3da5

Please sign in to comment.