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 committed Feb 15, 2022
1 parent f47b886 commit ff4dcdd
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
1 change: 1 addition & 0 deletions msgraph/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,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
69 changes: 69 additions & 0 deletions msgraph/serviceprincipals.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,75 @@ 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(struct {
KeyCredential KeyCredential `json:"keyCredential"`
}{
KeyCredential: 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{})

newGroupParent := msgraph.Group{
Expand Down Expand Up @@ -255,6 +259,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 ff4dcdd

Please sign in to comment.