Skip to content

Commit

Permalink
✨ add servicePrincipal methods AddTokenSigningCertificate and SetPref…
Browse files Browse the repository at this point in the history
…erredTokenSigningKeyThumbprint

This commit adds support to create the certificiate for Azure AD signed certs and set
the preferred token thumbprint on the service principal.

This will allow to follow the steps described in
https://docs.microsoft.com/en-us/graph/application-saml-sso-configure-api#create-a-signing-certificate
using hamilton SDK.

Currently Microsoft does not support a method to remove the created certificate key from the service principal.
https://docs.microsoft.com/en-us/graph/api/serviceprincipal-addtokensigningcertificate

Also manual removal via `removePassword` and `removekey` API calls are not supported and fail with an internal server error.

This SDK extension is the base to extend the `terraform-provider-azuread`.

Issue: hashicorp/terraform-provider-azuread#732
  • Loading branch information
dhohengassner authored and manicminer committed Apr 14, 2022
1 parent b5236d3 commit 221ac23
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 0 deletions.
2 changes: 2 additions & 0 deletions msgraph/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,7 @@ type KeyCredential struct {
EndDateTime *time.Time `json:"endDateTime,omitempty"`
KeyId *string `json:"keyId,omitempty"`
StartDateTime *time.Time `json:"startDateTime,omitempty"`
Thumbprint *string `json:"thumbprint,omitempty"`
Type KeyCredentialType `json:"type"`
Usage KeyCredentialUsage `json:"usage"`
Key *string `json:"key,omitempty"`
Expand Down Expand Up @@ -1282,6 +1283,7 @@ type ServicePrincipal struct {
PasswordCredentials *[]PasswordCredential `json:"passwordCredentials,omitempty"`
PasswordSingleSignOnSettings *PasswordSingleSignOnSettings `json:"passwordSingleSignOnSettings,omitempty"`
PreferredSingleSignOnMode *PreferredSingleSignOnMode `json:"preferredSingleSignOnMode,omitempty"`
PreferredTokenSigningKeyThumbprint *string `json:"preferredTokenSigningKeyThumbprint,omitempty"`
PreferredTokenSigningKeyEndDateTime *time.Time `json:"preferredTokenSigningKeyEndDateTime,omitempty"`
PublishedPermissionScopes *[]PermissionScope `json:"publishedPermissionScopes,omitempty"`
ReplyUrls *[]string `json:"replyUrls,omitempty"`
Expand Down
65 changes: 65 additions & 0 deletions msgraph/serviceprincipals.go
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,71 @@ func (c *ServicePrincipalsClient) RemovePassword(ctx context.Context, servicePri
return status, nil
}

// AddTokenSigningCertificate appends a new self signed certificate (keys and password) to a Service Principal.
func (c *ServicePrincipalsClient) AddTokenSigningCertificate(ctx context.Context, servicePrincipalId string, keyCredential KeyCredential) (*KeyCredential, int, error) {
var status int

body, err := json.Marshal(keyCredential)
if err != nil {
return nil, status, fmt.Errorf("json.Marshal(): %v", err)
}

resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{
Body: body,
ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc,
ValidStatusCodes: []int{http.StatusOK, http.StatusCreated},
Uri: Uri{
Entity: fmt.Sprintf("/servicePrincipals/%s/addTokenSigningCertificate", servicePrincipalId),
HasTenantId: true,
},
})
if err != nil {
return nil, status, fmt.Errorf("ServicePrincipalsClient.BaseClient.Post(): %v", err)
}

defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, status, fmt.Errorf("io.ReadAll(): %v", err)
}

var newKeyCredential KeyCredential
if err := json.Unmarshal(respBody, &newKeyCredential); err != nil {
return nil, status, fmt.Errorf("json.Unmarshal(): %v", err)
}

return &newKeyCredential, status, nil
}

// SetPreferredTokenSigningKeyThumbprint sets the field preferredTokenSigningKeyThumbprint for a Service Principal.
func (c *ServicePrincipalsClient) SetPreferredTokenSigningKeyThumbprint(ctx context.Context, servicePrincipalId string, thumbprint string) (int, error) {
var status int

body, err := json.Marshal(struct {
Thumbprint string `json:"preferredTokenSigningKeyThumbprint"`
}{
Thumbprint: thumbprint,
})
if err != nil {
return status, fmt.Errorf("json.Marshal(): %v", err)
}

_, status, _, err = c.BaseClient.Patch(ctx, PatchHttpRequestInput{
Body: body,
ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc,
ValidStatusCodes: []int{http.StatusNoContent},
Uri: Uri{
Entity: fmt.Sprintf("/servicePrincipals/%s", servicePrincipalId),
HasTenantId: true,
},
})
if err != nil {
return status, fmt.Errorf("ServicePrincipalsClient.BaseClient.Patch(): %v", err)
}

return status, nil
}

// ListOwnedObjects retrieves the owned objects of the specified Service Principal.
// id is the object ID of the service principal.
func (c *ServicePrincipalsClient) ListOwnedObjects(ctx context.Context, id string) (*[]string, int, error) {
Expand Down
36 changes: 36 additions & 0 deletions msgraph/serviceprincipals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ func TestServicePrincipalsClient(t *testing.T) {
testServicePrincipalsClient_Update(t, c, *sp)
pwd := testServicePrincipalsClient_AddPassword(t, c, sp)
testServicePrincipalsClient_RemovePassword(t, c, sp, pwd)

tsc := testServicePrincipalsClient_AddTokenSigningCertificate(t, c, sp)
testServicePrincipalsClient_SetPreferredTokenSigningKeyThumbprint(t, c, sp, *tsc.Thumbprint)

testServicePrincipalsClient_List(t, c, odata.Query{})

claimsMappingPolicy := testClaimsMappingPolicyClient_Create(t, c, msgraph.ClaimsMappingPolicy{
Expand Down Expand Up @@ -273,6 +277,38 @@ func testServicePrincipalsClient_AddPassword(t *testing.T, c *test.Test, a *msgr
return newPwd
}

func testServicePrincipalsClient_AddTokenSigningCertificate(t *testing.T, c *test.Test, a *msgraph.ServicePrincipal) *msgraph.KeyCredential {
expiry := time.Now().Add(24 * 90 * time.Hour)
tsc := msgraph.KeyCredential{
DisplayName: utils.StringPtr("test cert"),
EndDateTime: &expiry,
}
newKey, status, err := c.ServicePrincipalsClient.AddTokenSigningCertificate(c.Context, *a.ID, tsc)
if err != nil {
t.Fatalf("ServicePrincipalsClient.AddTokenSigningCertificate(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("ServicePrincipalsClient.AddTokenSigningCertificate(): invalid status: %d", status)
}

if newKey.Thumbprint == nil || len(*newKey.Thumbprint) == 0 {
t.Fatalf("ServicePrincipalsClient.AddTokenSigningCertificate(): nil or empty thumbprint returned by API")
}

return newKey
}

func testServicePrincipalsClient_SetPreferredTokenSigningKeyThumbprint(t *testing.T, c *test.Test, a *msgraph.ServicePrincipal, thumbprint string) {

status, err := c.ServicePrincipalsClient.SetPreferredTokenSigningKeyThumbprint(c.Context, *a.ID, thumbprint)
if err != nil {
t.Fatalf("ServicePrincipalsClient.AddTokenSigningCertificate(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("ServicePrincipalsClient.AddTokenSigningCertificate(): invalid status: %d", status)
}
}

func testServicePrincipalsClient_RemovePassword(t *testing.T, c *test.Test, a *msgraph.ServicePrincipal, p *msgraph.PasswordCredential) {
status, err := c.ServicePrincipalsClient.RemovePassword(c.Context, *a.ID, *p.KeyId)
if err != nil {
Expand Down

0 comments on commit 221ac23

Please sign in to comment.