-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
EntraUser provisioning resource (#91)
* EntraUser provisioning resource * Fix user creation bug in pulumi provisioner * Add ObjectMeta to EntraUser in test file
- Loading branch information
Showing
25 changed files
with
1,515 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -326,6 +326,48 @@ spec: | |
workspaceFriendlyName: Charisma | ||
``` | ||
|
||
### EntraUser | ||
|
||
`EntraUser` is a Custom Resource Definition (CRD) that represents a user for Entra Id. | ||
|
||
Definition can be found [here](./helm/crds/provisioning.totalsoft.ro_entrausers.yaml) | ||
|
||
## Spec | ||
|
||
The `EntraUser` spec has the following fields: | ||
|
||
- `userPrincipalName`: The user principal name of the user. This is typically the user's email address or username. | ||
- `displayName`: The display name of the user. | ||
- `initialPassword`: The initial password for the user. If this is not provided, a random password will be generated. | ||
- `domainRef`: The reference to the domain that the user belongs to. | ||
- `platformRef`: The reference to the platform that the user belongs to. | ||
|
||
## Example | ||
|
||
Here's an example of an `EntraUser` resource: | ||
|
||
```yaml | ||
apiVersion: provisioning.totalsoft.ro/v1alpha1 | ||
kind: EntraUser | ||
metadata: | ||
name: example-user | ||
namespace: qa-lsng | ||
spec: | ||
userPrincipalName: "[email protected]" | ||
displayName: "Example User" | ||
initialPassword: "password123" | ||
domainRef: "entra-users" | ||
platformRef: "qa" | ||
exports: | ||
- domain: entra-users | ||
initialPassword: | ||
toVault: | ||
keyTemplate: InitialPassword | ||
userPrincipalName: | ||
toVault: | ||
keyTemplate: UserPrincipalName | ||
``` | ||
|
||
## configuration.totalsoft.ro | ||
manages external configuration for the services in the platform, read more about from the [Twelve-Factor App ](https://12factor.net/config) methodology. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
--- | ||
apiVersion: apiextensions.k8s.io/v1 | ||
kind: CustomResourceDefinition | ||
metadata: | ||
annotations: | ||
controller-gen.kubebuilder.io/version: v0.14.0 | ||
name: entrausers.provisioning.totalsoft.ro | ||
spec: | ||
group: provisioning.totalsoft.ro | ||
names: | ||
kind: EntraUser | ||
listKind: EntraUserList | ||
plural: entrausers | ||
singular: entrauser | ||
scope: Namespaced | ||
versions: | ||
- additionalPrinterColumns: | ||
- jsonPath: .spec.displayName | ||
name: Display name | ||
type: string | ||
- jsonPath: .spec.userPrincipalName | ||
name: User principal name | ||
type: string | ||
- jsonPath: .spec.platformRef | ||
name: Platform | ||
type: string | ||
- jsonPath: .spec.domainRef | ||
name: Domain | ||
type: string | ||
name: v1alpha1 | ||
schema: | ||
openAPIV3Schema: | ||
properties: | ||
apiVersion: | ||
description: |- | ||
APIVersion defines the versioned schema of this representation of an object. | ||
Servers should convert recognized schemas to the latest internal value, and | ||
may reject unrecognized values. | ||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | ||
type: string | ||
kind: | ||
description: |- | ||
Kind is a string value representing the REST resource this object represents. | ||
Servers may infer this from the endpoint the client submits requests to. | ||
Cannot be updated. | ||
In CamelCase. | ||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | ||
type: string | ||
metadata: | ||
type: object | ||
spec: | ||
properties: | ||
dependsOn: | ||
description: List of dependencies | ||
items: | ||
properties: | ||
kind: | ||
description: Kind is a string value representing the REST resource | ||
this dependency represents. | ||
type: string | ||
name: | ||
description: ' The name of the dependency.' | ||
type: string | ||
required: | ||
- kind | ||
- name | ||
type: object | ||
type: array | ||
displayName: | ||
description: DisplayName represents the display name of the user, | ||
e.g. "John Doe" | ||
type: string | ||
domainRef: | ||
description: Business Domain that this resource is provision for. | ||
type: string | ||
exports: | ||
description: Export provisioning values spec. | ||
items: | ||
properties: | ||
domain: | ||
description: The domain or bounded-context in which this database | ||
will be used. | ||
type: string | ||
initialPassword: | ||
description: The initial password for the user | ||
properties: | ||
toConfigMap: | ||
properties: | ||
keyTemplate: | ||
type: string | ||
required: | ||
- keyTemplate | ||
type: object | ||
toVault: | ||
properties: | ||
keyTemplate: | ||
type: string | ||
required: | ||
- keyTemplate | ||
type: object | ||
type: object | ||
userPrincipalName: | ||
description: The user principal name | ||
properties: | ||
toConfigMap: | ||
properties: | ||
keyTemplate: | ||
type: string | ||
required: | ||
- keyTemplate | ||
type: object | ||
toVault: | ||
properties: | ||
keyTemplate: | ||
type: string | ||
required: | ||
- keyTemplate | ||
type: object | ||
type: object | ||
required: | ||
- domain | ||
type: object | ||
type: array | ||
initialPassword: | ||
description: InitialPassword represents the initial password for the | ||
user | ||
type: string | ||
platformRef: | ||
description: Target platform (custom resource name). | ||
type: string | ||
target: | ||
default: | ||
category: Tenant | ||
description: The provisioning target. | ||
properties: | ||
category: | ||
default: Tenant | ||
description: 'Provisioning target type. Possible values: Tenant, | ||
Platform' | ||
enum: | ||
- Tenant | ||
- Platform | ||
type: string | ||
filter: | ||
description: |- | ||
Filter targets (applies for category "Tenant"). | ||
If ommited all targets are selected. | ||
properties: | ||
kind: | ||
default: Blacklist | ||
description: 'Includes or excludes the speciffied targets. | ||
Possibile values: Blacklist, Whitelist' | ||
enum: | ||
- Blacklist | ||
- Whitelist | ||
type: string | ||
values: | ||
description: A list of targets to include or exculde | ||
items: | ||
type: string | ||
type: array | ||
required: | ||
- kind | ||
type: object | ||
required: | ||
- category | ||
type: object | ||
tenantOverrides: | ||
additionalProperties: | ||
x-kubernetes-preserve-unknown-fields: true | ||
description: |- | ||
Overrides for tenants. Dictionary with tenant name as key, spec override as value. | ||
The spec override has the same structure as Spec | ||
type: object | ||
userPrincipalName: | ||
description: UserPrincipalName represents the user principal name, | ||
e.g. "[email protected]" | ||
type: string | ||
required: | ||
- displayName | ||
- domainRef | ||
- platformRef | ||
- target | ||
- userPrincipalName | ||
type: object | ||
required: | ||
- spec | ||
type: object | ||
served: true | ||
storage: true | ||
subresources: {} |
67 changes: 67 additions & 0 deletions
67
internal/controllers/provisioning/provisioners/pulumi/entra_user.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package pulumi | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/pulumi/pulumi-azuread/sdk/v5/go/azuread" | ||
"github.com/pulumi/pulumi-random/sdk/v4/go/random" | ||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi" | ||
"totalsoft.ro/platform-controllers/internal/controllers/provisioning" | ||
provisioningv1 "totalsoft.ro/platform-controllers/pkg/apis/provisioning/v1alpha1" | ||
) | ||
|
||
func deployEntraUser(target provisioning.ProvisioningTarget, | ||
entraUser *provisioningv1.EntraUser, | ||
dependencies []pulumi.Resource, | ||
ctx *pulumi.Context) (*azuread.User, error) { | ||
|
||
valueExporter := handleValueExport(target) | ||
gvk := provisioningv1.SchemeGroupVersion.WithKind("AzureDatabase") | ||
|
||
initialPassword := pulumi.String(entraUser.Spec.InitialPassword).ToStringOutput() | ||
if entraUser.Spec.InitialPassword == "" { | ||
randomPassword, err := random.NewRandomPassword(ctx, fmt.Sprintf("%s-initial-password", entraUser.Spec.UserPrincipalName), &random.RandomPasswordArgs{ | ||
Length: pulumi.Int(10), | ||
Upper: pulumi.Bool(true), | ||
MinUpper: pulumi.Int(1), | ||
Lower: pulumi.Bool(true), | ||
MinLower: pulumi.Int(1), | ||
Numeric: pulumi.Bool(true), | ||
MinNumeric: pulumi.Int(1), | ||
Special: pulumi.Bool(true), | ||
MinSpecial: pulumi.Int(1), | ||
}) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
initialPassword = randomPassword.Result | ||
} | ||
|
||
user, err := azuread.NewUser(ctx, entraUser.Name, &azuread.UserArgs{ | ||
UserPrincipalName: pulumi.String(entraUser.Spec.UserPrincipalName), | ||
DisplayName: pulumi.String(entraUser.Spec.DisplayName), | ||
Password: initialPassword, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, exp := range entraUser.Spec.Exports { | ||
domain := exp.Domain | ||
if domain == "" { | ||
domain = entraUser.Spec.DomainRef | ||
} | ||
|
||
err = valueExporter(newExportContext(ctx, domain, entraUser.Name, entraUser.ObjectMeta, gvk), | ||
map[string]exportTemplateWithValue{ | ||
"initialPassword": {exp.InitialPassword, initialPassword}, | ||
"userPrincipalName": {exp.UserPrincipalName, pulumi.String(entraUser.Spec.UserPrincipalName)}, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
return user, nil | ||
} |
39 changes: 39 additions & 0 deletions
39
internal/controllers/provisioning/provisioners/pulumi/entra_user_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package pulumi | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi" | ||
"github.com/stretchr/testify/assert" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
provisioningv1 "totalsoft.ro/platform-controllers/pkg/apis/provisioning/v1alpha1" | ||
) | ||
|
||
func TestDeployEntraUser(t *testing.T) { | ||
t.Run("maximal entra user spec", func(t *testing.T) { | ||
platform := "dev" | ||
tenant := newTenant("tenant1", platform) | ||
entraUser := &provisioningv1.EntraUser{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "my-entra-user", | ||
}, | ||
Spec: provisioningv1.EntraUserSpec{ | ||
UserPrincipalName: "[email protected]", | ||
DisplayName: "Example User", | ||
InitialPassword: "password123", | ||
ProvisioningMeta: provisioningv1.ProvisioningMeta{ | ||
DomainRef: "example-domain", | ||
}, | ||
}, | ||
} | ||
|
||
err := pulumi.RunErr(func(ctx *pulumi.Context) error { | ||
user, err := deployEntraUser(tenant, entraUser, []pulumi.Resource{}, ctx) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, user) | ||
return nil | ||
|
||
}, pulumi.WithMocks("project", "stack", mocks(0))) | ||
assert.NoError(t, err) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.