Skip to content

Commit bef00a8

Browse files
author
Nicholas M. Iodice
authored
Prevent Service Principal password from being reset to "" for updates (#296)
* Prevent Service Principal password from being reset to "" for updates This change fixes a bug that was caused when there was an update applied to a service connection. The bug would result in the service principal secret being reset to the empty string. This change fixes the problem by catching this case and setting the value to "null", which indicates to the AzDO services that the password should not be updated.
1 parent 714b998 commit bef00a8

File tree

2 files changed

+65
-10
lines changed

2 files changed

+65
-10
lines changed

azuredevops/resource_serviceendpoint_azurerm.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func expandServiceEndpointAzureRM(d *schema.ResourceData) (*serviceendpoint.Serv
8989
if _, ok := d.GetOk("credentials"); ok {
9090
credentials := d.Get("credentials").([]interface{})[0].(map[string]interface{})
9191
(*serviceEndpoint.Authorization.Parameters)["serviceprincipalid"] = credentials["serviceprincipalid"].(string)
92-
(*serviceEndpoint.Authorization.Parameters)["serviceprincipalkey"] = credentials["serviceprincipalkey"].(string)
92+
(*serviceEndpoint.Authorization.Parameters)["serviceprincipalkey"] = expandSpnKey(credentials)
9393
(*serviceEndpoint.Data)["creationMode"] = "Manual"
9494
}
9595

@@ -98,6 +98,22 @@ func expandServiceEndpointAzureRM(d *schema.ResourceData) (*serviceendpoint.Serv
9898
return serviceEndpoint, projectID
9999
}
100100

101+
func expandSpnKey(credentials map[string]interface{}) string {
102+
// Note: if this is an update for a field other than `serviceprincipalkey`, the `serviceprincipalkey` will be
103+
// set to `""`. Without catching this case and setting the value to `"null"`, the `serviceprincipalkey` will
104+
// actually be set to `""` by the Azure DevOps service.
105+
//
106+
// This step is critical in order to ensure that the service connection can update without loosing its password!
107+
//
108+
// This behavior is unfortunately not documented in the API documentation.
109+
spnKey, ok := credentials["serviceprincipalkey"]
110+
if !ok || spnKey.(string) == "" {
111+
return "null"
112+
}
113+
114+
return spnKey.(string)
115+
}
116+
101117
func flattenCredentials(serviceEndpoint *serviceendpoint.ServiceEndpoint, hashKey string, hashValue string) interface{} {
102118
return []map[string]interface{}{{
103119
"serviceprincipalid": (*serviceEndpoint.Authorization.Parameters)["serviceprincipalid"],

azuredevops/resource_serviceendpoint_azurerm_test.go

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ var azurermTestServiceEndpointAzureRMID = uuid.New()
2626
var azurermRandomServiceEndpointAzureRMProjectID = uuid.New().String()
2727
var azurermTestServiceEndpointAzureRMProjectID = &azurermRandomServiceEndpointAzureRMProjectID
2828

29-
var azurermTestServiceEndpointsAzureRM = []serviceendpoint.ServiceEndpoint{
30-
{
29+
func getManualAuthServiceEndpoint() serviceendpoint.ServiceEndpoint {
30+
return serviceendpoint.ServiceEndpoint{
3131
Authorization: &serviceendpoint.EndpointAuthorization{
3232
Parameters: &map[string]string{
3333
"authenticationType": "spnKey",
34-
"serviceprincipalid": "",
35-
"serviceprincipalkey": "",
34+
"serviceprincipalid": "e31eaaac-47da-4156-b433-9b0538c94b7e", //fake value
35+
"serviceprincipalkey": "d96d8515-20b2-4413-8879-27c5d040cbc2", //fake value
3636
"tenantid": "aba07645-051c-44b4-b806-c34d33f3dcd1", //fake value
3737
},
3838
Scheme: converter.String("ServicePrincipal"),
3939
},
4040
Data: &map[string]string{
41-
"creationMode": "Automatic",
41+
"creationMode": "Manual",
4242
"environment": "AzureCloud",
4343
"scopeLevel": "Subscription",
4444
"subscriptionId": "42125daf-72fd-417c-9ea7-080690625ad3", //fake value
@@ -50,19 +50,23 @@ var azurermTestServiceEndpointsAzureRM = []serviceendpoint.ServiceEndpoint{
5050
Owner: converter.String("library"), // Supported values are "library", "agentcloud"
5151
Type: converter.String("azurerm"),
5252
Url: converter.String("https://management.azure.com/"),
53-
},
53+
}
54+
}
55+
56+
var azurermTestServiceEndpointsAzureRM = []serviceendpoint.ServiceEndpoint{
57+
getManualAuthServiceEndpoint(),
5458
{
5559
Authorization: &serviceendpoint.EndpointAuthorization{
5660
Parameters: &map[string]string{
5761
"authenticationType": "spnKey",
58-
"serviceprincipalid": "e31eaaac-47da-4156-b433-9b0538c94b7e", //fake value
59-
"serviceprincipalkey": "d96d8515-20b2-4413-8879-27c5d040cbc2", //fake value
62+
"serviceprincipalid": "",
63+
"serviceprincipalkey": "",
6064
"tenantid": "aba07645-051c-44b4-b806-c34d33f3dcd1", //fake value
6165
},
6266
Scheme: converter.String("ServicePrincipal"),
6367
},
6468
Data: &map[string]string{
65-
"creationMode": "Manual",
69+
"creationMode": "Automatic",
6670
"environment": "AzureCloud",
6771
"scopeLevel": "Subscription",
6872
"subscriptionId": "42125daf-72fd-417c-9ea7-080690625ad3", //fake value
@@ -224,6 +228,41 @@ func TestAzureDevOpsServiceEndpointAzureRM_Update_DoesNotSwallowError(t *testing
224228
}
225229
}
226230

231+
func TestAzureDevOpsServiceEndpointAzureRM_ExpandCredentials(t *testing.T) {
232+
spnKeyExistsWithValue := map[string]interface{}{"serviceprincipalkey": "fake-spn-key"}
233+
spnKeyExistsWithEmptyValue := map[string]interface{}{"serviceprincipalkey": ""}
234+
spnKeyDoesNotExists := map[string]interface{}{}
235+
236+
require.Equal(t, expandSpnKey(spnKeyExistsWithValue), "fake-spn-key")
237+
require.Equal(t, expandSpnKey(spnKeyExistsWithEmptyValue), "null")
238+
require.Equal(t, expandSpnKey(spnKeyDoesNotExists), "null")
239+
}
240+
241+
// This is a little different than most. The steps done, along with the motivation behind each, are as follows:
242+
// (1) The service endpoint is configured. The `serviceprincipalkey` is set to `""`, which matches
243+
// the Azure DevOps API behavior. The service will intentionally hide the value of
244+
// `serviceprincipalkey` because it is a secret value
245+
// (2) The resource is flattened/expanded
246+
// (3) The `serviceprincipalkey` field is inspected and asserted to equal `"null"`. This special
247+
// value, which is unfortunately not documented in the REST API, will be interpreted by the
248+
// Azure DevOps API as an indicator to "not update" the field. The resulting behavior is that
249+
// this Terraform Resource will be able to update the Service Endpoint without needing to
250+
// pass the password along in each request.
251+
func TestAzureDevOpsServiceEndpointAzureRM_ExpandHandlesMissingSpnKeyInAPIResponse(t *testing.T) {
252+
// step (1)
253+
endpoint := getManualAuthServiceEndpoint()
254+
resourceData := getResourceData(t, endpoint)
255+
(*endpoint.Authorization.Parameters)["serviceprincipalkey"] = ""
256+
257+
// step (2)
258+
flattenServiceEndpointAzureRM(resourceData, &endpoint, azurermTestServiceEndpointAzureRMProjectID)
259+
expandedEndpoint, _ := expandServiceEndpointAzureRM(resourceData)
260+
261+
// step (3)
262+
spnKeyProperty := (*expandedEndpoint.Authorization.Parameters)["serviceprincipalkey"]
263+
require.Equal(t, "null", spnKeyProperty)
264+
}
265+
227266
/**
228267
* Begin acceptance tests
229268
*/

0 commit comments

Comments
 (0)