Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support hexadecimal and base64 encoded DER certificates #386

Merged
merged 2 commits into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 91 additions & 2 deletions docs/resources/application_certificate.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Manages a Certificate associated with an Application within Azure Active Directo

## Example Usage

*Using a PEM certificate*

```hcl
resource "azuread_application" "example" {
name = "example"
Expand All @@ -23,20 +25,107 @@ resource "azuread_application_certificate" "example" {
}
```

*Using a DER certificate*

```hcl
resource "azuread_application" "example" {
name = "example"
}

resource "azuread_application_certificate" "example" {
application_object_id = azuread_application.example.id
type = "AsymmetricX509Cert"
encoding = "base64"
value = base64encode(file("cert.der"))
end_date = "2021-05-01T01:02:03Z"
}
```

### Using a certificate from Azure Key Vault

```hcl
resource "azurerm_key_vault_certificate" "example" {
name = "generated-cert"
key_vault_id = azurerm_key_vault.example.id

certificate_policy {
issuer_parameters {
name = "Self"
}

key_properties {
exportable = true
key_size = 2048
key_type = "RSA"
reuse_key = true
}

lifetime_action {
action {
action_type = "AutoRenew"
}

trigger {
days_before_expiry = 30
}
}

secret_properties {
content_type = "application/x-pkcs12"
}

x509_certificate_properties {
extended_key_usage = ["1.3.6.1.5.5.7.3.2"]

key_usage = [
"dataEncipherment",
"digitalSignature",
"keyCertSign",
"keyEncipherment",
]

subject_alternative_names {
dns_names = ["internal.contoso.com", "domain.hello.world"]
}

subject = "CN=${azuread_application.example.name}"
validity_in_months = 12
}
}
}

resource "azuread_application" "example" {
name = "example"
}

resource "azuread_application_certificate" "example" {
application_object_id = azuread_application.example.id
type = "AsymmetricX509Cert"
encoding = "hex"
value = azurerm_key_vault_certificate.example.certificate_data
end_date = azurerm_key_vault_certificate.example.certificate_attribute[0].expires
start_date = azurerm_key_vault_certificate.example.certificate_attribute[0].not_before
}
```

## Argument Reference

The following arguments are supported:

* `application_object_id` - (Required) The Object ID of the Application for which this Certificate should be created. Changing this field forces a new resource to be created.
* `encoding` - (Optional) Specifies the encoding used for the supplied certificate data. Must be one of `pem`, `base64` or `hex`. Defaults to `pem`.

-> **NOTE:** The `hex` encoding option is useful for consuming certificate data from the [azurerm_key_vault_certificate](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault_certificate) resource.

* `end_date` - (Optional) The End Date which the Certificate is valid until, formatted as a RFC3339 date string (e.g. `2018-01-01T01:02:03Z`). Changing this field forces a new resource to be created.
* `end_date_relative` - (Optional) A relative duration for which the Certificate is valid until, for example `240h` (10 days) or `2400h30m`. Changing this field forces a new resource to be created.

~> **NOTE:** One of `end_date` or `end_date_relative` must be set. The maximum duration is one year.
~> **NOTE:** One of `end_date` or `end_date_relative` must be set. The maximum duration is enforced by Azure AD.

* `key_id` - (Optional) A GUID used to uniquely identify this Certificate. If not specified a GUID will be created. Changing this field forces a new resource to be created.
* `start_date` - (Optional) The Start Date which the Certificate is valid from, formatted as a RFC3339 date string (e.g. `2018-01-01T01:02:03Z`). If this isn't specified, the current date is used. Changing this field forces a new resource to be created.
* `type` - (Required) The type of key/certificate. Must be one of `AsymmetricX509Cert` or `Symmetric`. Changing this fields forces a new resource to be created.
* `value` - (Required) The Certificate for this Service Principal.
* `value` - (Required) The certificate data, which can be PEM encoded, base64 encoded DER or hexadecimal encoded DER. See also the `encoding` argument.

## Attributes Reference

Expand Down
30 changes: 28 additions & 2 deletions docs/resources/service_principal_certificate.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Manages a Certificate associated with a Service Principal within Azure Active Di

## Example Usage

*Using a PEM certificate*

```hcl
resource "azuread_application" "example" {
name = "example"
Expand All @@ -27,20 +29,44 @@ resource "azuread_service_principal_certificate" "example" {
}
```

*Using a DER certificate*

```hcl
resource "azuread_application" "example" {
name = "example"
}

resource "azuread_service_principal" "example" {
application_id = azuread_application.example.application_id
}

resource "azuread_service_principal_certificate" "example" {
service_principal_id = azuread_service_principal.example.id
type = "AsymmetricX509Cert"
encoding = "base64"
value = base64encode(file("cert.der"))
end_date = "2021-05-01T01:02:03Z"
}
```

## Argument Reference

The following arguments are supported:

* `encoding` - (Optional) Specifies the encoding used for the supplied certificate data. Must be one of `pem`, `base64` or `hex`. Defaults to `pem`.

-> **NOTE:** The `hex` encoding option is useful for consuming certificate data from the [azurerm_key_vault_certificate](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault_certificate) resource.

* `end_date` - (Optional) The End Date which the Certificate is valid until, formatted as a RFC3339 date string (e.g. `2018-01-01T01:02:03Z`). Changing this field forces a new resource to be created.
* `end_date_relative` - (Optional) A relative duration for which the Certificate is valid until, for example `240h` (10 days) or `2400h30m`. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Changing this field forces a new resource to be created.

~> **NOTE:** One of `end_date` or `end_date_relative` must be set. The maximum duration is one year.
~> **NOTE:** One of `end_date` or `end_date_relative` must be set. The maximum duration is enforced by Azure AD.

* `key_id` - (Optional) A GUID used to uniquely identify this Certificate. If not specified a GUID will be created. Changing this field forces a new resource to be created.
* `service_principal_id` - (Required) The ID of the Service Principal for which this certificate should be created. Changing this field forces a new resource to be created.
* `start_date` - (Optional) The Start Date which the Certificate is valid from, formatted as a RFC3339 date string (e.g. `2018-01-01T01:02:03Z`). If this isn't specified, the current date is used. Changing this field forces a new resource to be created.
* `type` - (Required) The type of key/certificate. Must be one of `AsymmetricX509Cert` or `Symmetric`. Changing this fields forces a new resource to be created.
* `value` - (Required) The Certificate for this Service Principal.
* `value` - (Required) The certificate data, which can be PEM encoded, base64 encoded DER or hexadecimal encoded DER. See also the `encoding` argument.

## Attributes Reference

Expand Down
53 changes: 52 additions & 1 deletion internal/helpers/aadgraph/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package aadgraph
import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
Expand Down Expand Up @@ -46,6 +49,18 @@ func CertificateResourceSchema(idAttribute string) map[string]*schema.Schema {
}, false),
},

"encoding": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "pem",
ValidateFunc: validation.StringInSlice([]string{
"base64",
"hex",
"pem",
}, false),
},

"value": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -291,7 +306,43 @@ func WaitForPasswordCredentialReplication(ctx context.Context, keyId string, tim
func KeyCredentialForResource(d *schema.ResourceData) (*graphrbac.KeyCredential, error) {
keyType := d.Get("type").(string)
value := d.Get("value").(string)
encodedValue := base64.StdEncoding.EncodeToString([]byte(value))

var encodedValue string
encoding := d.Get("encoding").(string)
switch encoding {
case "base64":
der, err := base64.StdEncoding.DecodeString(strings.TrimSpace(value))
if err != nil {
return nil, fmt.Errorf("failed to decode base64 certificate data")
}
block := pem.Block{
Type: "CERTIFICATE",
Bytes: der,
}
pemVal := pem.EncodeToMemory(&block)
if pemVal == nil {
return nil, fmt.Errorf("failed to PEM-encode certificate")
}
encodedValue = base64.StdEncoding.EncodeToString(pemVal)
case "hex":
bytesVal := []byte(strings.TrimSpace(value))
der := make([]byte, hex.DecodedLen(len(bytesVal)))
_, err := hex.Decode(der, bytesVal)
if err != nil {
return nil, fmt.Errorf("failed to decode hexadecimal certificate data: %+v", err)
}
block := pem.Block{
Type: "CERTIFICATE",
Bytes: der,
}
pemVal := pem.EncodeToMemory(&block)
if pemVal == nil {
return nil, fmt.Errorf("failed to PEM-encode certificate")
}
encodedValue = base64.StdEncoding.EncodeToString(pemVal)
case "pem":
encodedValue = base64.StdEncoding.EncodeToString([]byte(value))
}

// errors should be handled by the validation
var keyId string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
"github.com/terraform-providers/terraform-provider-azuread/internal/utils"
)

const testCertificateApplication string = `-----BEGIN CERTIFICATE-----
const applicationCertificatePem string = `-----BEGIN CERTIFICATE-----
MIIDGjCCAgICCQDAQlCA1jw1BjANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExFzAVBgNVBAoMDkhhc2hpQ29ycCwgSW5jMRowGAYDVQQD
DBFoYXNoaWNvcnB0ZXN0LmNvbTAeFw0yMDA1MzEyMDI2MTFaFw0yMTA1MzEyMDI2
Expand All @@ -37,6 +37,10 @@ HraQzsK7BNxC5NSwwirT95JH+Xd8rvWu+bCveJz3mnZ3sgolCoxL6Hv1uD2UOZb5
rCHdW31vp5PYNJaSkYL0j259Ogb8crkIzDr3Z8YF
-----END CERTIFICATE-----`

const applicationCertificateBase64 string = `MIIDLDCCAhSgAwIBAgIQLSZ4E7hXTw+nb8YavHIoLjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDEw10ZXN0cmcta3YtYXBwMB4XDTIxMDExOTE4MjczMVoXDTIyMDExOTE4MzczMVowGDEWMBQGA1UEAxMNdGVzdHJnLWt2LWFwcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL+HfZqmm57ngIYuzQBWnH2Yw/7u4h5xJ+F4/7U5nGABcUJQOMH+bXpZUz6LpwaXfQ70l8zmEPQv2qIfDs8TzcH0DOi2mOgM8eQoaUkOUeu4AXBBcRcVgTURH5HkSbEYMsxyaiinrvn5+KoQJcgVj8dZdcN+YxZr+ZgTaHGxjirTJEt6aGt+zr2gsZi8m8qGAQuIJbhPvBUk36VmriEIQR3ReigjT0yRCwBezsXL7EZ+WEdZB6p2UFGkXLq7coSkEA9UHLB0pMtLn74RbN6S395VnW4Vk3fgSfinysfIdro5UChC9R6OA9pWSgR0dxRw5AO0JMU8YHZnsajedpGREUUCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgG+MAkGA1UdEwQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUFQUOvXHBPa0ZpNNwARFn0+k4dD4wHQYDVR0OBBYEFBUFDr1xwT2tGaTTcAERZ9PpOHQ+MA0GCSqGSIb3DQEBCwUAA4IBAQCf1LwnUVoBoYluey2kTn5rdI4As0pMg9nfec8xAWh3BjbYTjElcce+IP73TLzTPLe1lR2PlY/QcHuvfx8Orkm4JLBHrIUEcDh+G12qjMKU1GYtSUFj/QYwAPvesjryO0ow2XP+JgTj4yxVyXTcYfwT4t7gBlV50B0BXY/us3Mu/laczlN+xIonIPzIX5ZZQZBwDNmc1EPUjSN7KZ9AzvrMB6EcXYrP7IM7xEJzhCb/zgdIgGYGp0sMxOb8EZnMnIYmForwLbvryAZ3iN1RPCmxjXPRex1IfWxUY1PhCLjU3LUch6aHHx3YYp/GMg8j5DlfyD4WtIxqIUpJP5uE/e8Q`

const applicationCertificateHex string = `3082032C30820214A00302010202102D267813B8574F0FA76FC61ABC72282E300D06092A864886F70D01010B05003018311630140603550403130D7465737472672D6B762D617070301E170D3231303131393138323733315A170D3232303131393138333733315A3018311630140603550403130D7465737472672D6B762D61707030820122300D06092A864886F70D01010105000382010F003082010A0282010100BF877D9AA69B9EE780862ECD00569C7D98C3FEEEE21E7127E178FFB5399C600171425038C1FE6D7A59533E8BA706977D0EF497CCE610F42FDAA21F0ECF13CDC1F40CE8B698E80CF1E42869490E51EBB80170417117158135111F91E449B11832CC726A28A7AEF9F9F8AA1025C8158FC75975C37E63166BF998136871B18E2AD3244B7A686B7ECEBDA0B198BC9BCA86010B8825B84FBC1524DFA566AE2108411DD17A28234F4C910B005ECEC5CBEC467E58475907AA765051A45CBABB7284A4100F541CB074A4CB4B9FBE116CDE92DFDE559D6E159377E049F8A7CAC7C876BA39502842F51E8E03DA564A0474771470E403B424C53C607667B1A8DE76919111450203010001A3723070300E0603551D0F0101FF0404030201BE30090603551D130402300030130603551D25040C300A06082B06010505070301301F0603551D2304183016801415050EBD71C13DAD19A4D370011167D3E938743E301D0603551D0E0416041415050EBD71C13DAD19A4D370011167D3E938743E300D06092A864886F70D01010B050003820101009FD4BC27515A01A1896E7B2DA44E7E6B748E00B34A4C83D9DF79CF310168770636D84E312571C7BE20FEF74CBCD33CB7B5951D8F958FD0707BAF7F1F0EAE49B824B047AC850470387E1B5DAA8CC294D4662D494163FD063000FBDEB23AF23B4A30D973FE2604E3E32C55C974DC61FC13E2DEE0065579D01D015D8FEEB3732EFE569CCE537EC48A2720FCC85F96594190700CD99CD443D48D237B299F40CEFACC07A11C5D8ACFEC833BC442738426FFCE0748806606A74B0CC4E6FC1199CC9C8626168AF02DBBEBC8067788DD513C29B18D73D17B1D487D6C546353E108B8D4DCB51C87A6871F1DD8629FC6320F23E4395FC83E16B48C6A214A493F9B84FDEF10`

type ApplicationCertificateResource struct{}

func TestAccApplicationCertificate_basic(t *testing.T) {
Expand All @@ -52,7 +56,7 @@ func TestAccApplicationCertificate_basic(t *testing.T) {
check.That(data.ResourceName).Key("key_id").Exists(),
),
},
data.ImportStep("end_date_relative", "value"),
data.ImportStep("encoding", "end_date_relative", "value"),
})
}

Expand All @@ -70,7 +74,41 @@ func TestAccApplicationCertificate_complete(t *testing.T) {
check.That(data.ResourceName).Key("key_id").Exists(),
),
},
data.ImportStep("end_date_relative", "value"),
data.ImportStep("encoding", "end_date_relative", "value"),
})
}

func TestAccApplicationCertificate_base64Cert(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_application_certificate", "test")
endDate := time.Now().AddDate(0, 3, 27).UTC().Format(time.RFC3339)
r := ApplicationCertificateResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.base64Cert(data, endDate),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("key_id").Exists(),
),
},
data.ImportStep("encoding", "end_date_relative", "value"),
})
}

func TestAccApplicationCertificate_hexCert(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_application_certificate", "test")
endDate := time.Now().AddDate(0, 3, 27).UTC().Format(time.RFC3339)
r := ApplicationCertificateResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.hexCert(data, endDate),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("key_id").Exists(),
),
},
data.ImportStep("encoding", "end_date_relative", "value"),
})
}

Expand All @@ -87,7 +125,7 @@ func TestAccApplicationCertificate_relativeEndDate(t *testing.T) {
check.That(data.ResourceName).Key("end_date").Exists(),
),
},
data.ImportStep("end_date_relative", "value"),
data.ImportStep("encoding", "end_date_relative", "value"),
})
}

Expand Down Expand Up @@ -155,7 +193,7 @@ resource "azuread_application_certificate" "test" {
%[3]s
EOT
}
`, r.template(data), endDate, testCertificateApplication)
`, r.template(data), endDate, applicationCertificatePem)
}

func (r ApplicationCertificateResource) complete(data acceptance.TestData, startDate, endDate string) string {
Expand All @@ -168,11 +206,44 @@ resource "azuread_application_certificate" "test" {
type = "AsymmetricX509Cert"
start_date = "%[3]s"
end_date = "%[4]s"
encoding = "pem"
value = <<EOT
%[5]s
EOT
}
`, r.template(data), data.RandomID, startDate, endDate, testCertificateApplication)
`, r.template(data), data.RandomID, startDate, endDate, applicationCertificatePem)
}

func (r ApplicationCertificateResource) base64Cert(data acceptance.TestData, endDate string) string {
return fmt.Sprintf(`
%[1]s

resource "azuread_application_certificate" "test" {
application_object_id = azuread_application.test.id
type = "AsymmetricX509Cert"
end_date = "%[2]s"
encoding = "base64"
value = <<EOT
%[3]s
EOT
}
`, r.template(data), endDate, applicationCertificateBase64)
}

func (r ApplicationCertificateResource) hexCert(data acceptance.TestData, endDate string) string {
return fmt.Sprintf(`
%[1]s

resource "azuread_application_certificate" "test" {
application_object_id = azuread_application.test.id
type = "AsymmetricX509Cert"
end_date = "%[2]s"
encoding = "hex"
value = <<EOT
%[3]s
EOT
}
`, r.template(data), endDate, applicationCertificateHex)
}

func (r ApplicationCertificateResource) relativeEndDate(data acceptance.TestData) string {
Expand All @@ -187,7 +258,7 @@ resource "azuread_application_certificate" "test" {
%[2]s
EOT
}
`, r.template(data), testCertificateApplication)
`, r.template(data), applicationCertificatePem)
}

func (r ApplicationCertificateResource) requiresImport(data acceptance.TestData, endDate string) string {
Expand Down
Loading