Skip to content

Commit

Permalink
binary_authorization_attestor KMS support (#2083)
Browse files Browse the repository at this point in the history
Merged PR #2083.
  • Loading branch information
drebes authored and modular-magician committed Jul 26, 2019
1 parent 8961223 commit 1fd396d
Show file tree
Hide file tree
Showing 13 changed files with 447 additions and 18 deletions.
2 changes: 1 addition & 1 deletion build/terraform
2 changes: 1 addition & 1 deletion build/terraform-beta
2 changes: 1 addition & 1 deletion build/terraform-mapper
41 changes: 35 additions & 6 deletions products/binaryauthorization/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,47 @@ objects:
- !ruby/object:Api::Type::String
name: id
description: |
This field will be overwritten with key ID information, for
example, an identifier extracted from a PGP public key. This
field may not be updated.
output: true
The ID of this public key. Signatures verified by BinAuthz
must include the ID of the public key that can be used to
verify them, and that ID must match the contents of this
field exactly. Additional restrictions on this field can
be imposed based on which public key type is encapsulated.
See the documentation on publicKey cases below for details.
- !ruby/object:Api::Type::String
name: asciiArmoredPgpPublicKey
description: |
ASCII-armored representation of a PGP public key, as the
entire output by the command
`gpg --export --armor foo@example.com` (either LF or CRLF
line endings).
required: true
line endings). When using this field, id should be left
blank. The BinAuthz API handlers will calculate the ID
and fill it in automatically. BinAuthz computes this ID
as the OpenPGP RFC4880 V4 fingerprint, represented as
upper-case hex. If id is provided by the caller, it will
be overwritten by the API-calculated ID.
- !ruby/object:Api::Type::NestedObject
name: pkixPublicKey
description: |
A raw PKIX SubjectPublicKeyInfo format public key.
NOTE: id may be explicitly provided by the caller when using this
type of public key, but it MUST be a valid RFC3986 URI. If id is left
blank, a default one will be computed based on the digest of the DER
encoding of the public key.
properties:
- !ruby/object:Api::Type::String
name: publicKeyPem
description: |
A PEM-encoded public key, as described in
`https://tools.ietf.org/html/rfc7468#section-13`
- !ruby/object:Api::Type::String
name: signatureAlgorithm
description: |
The signature algorithm used to verify a message against
a signature using this key. These signature algorithm must
match the structure and any object identifiers encoded in
publicKeyPem (i.e. this algorithm must match that of the
public key).
- !ruby/object:Api::Type::String
name: delegationServiceAccountEmail
description: |
Expand Down
9 changes: 9 additions & 0 deletions products/binaryauthorization/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ overrides: !ruby/object:Overrides::ResourceOverrides
vars:
attestor_name: "test-attestor"
note_name: "test-attestor-note"
- !ruby/object:Provider::Terraform::Examples
name: "binary_authorization_attestor_kms"
primary_resource_id: "attestor"
skip_test: true
vars:
attestor_name: "test-attestor"
note_name: "test-attestor-note"
key_name: "test-attestor-key"
keyring_name: "test-attestor-key-ring"
custom_code: !ruby/object:Provider::Terraform::CustomCode
encoder: templates/terraform/encoders/binauth_attestor_note_field_name.go.erb
decoder: templates/terraform/decoders/binauth_attestor_note_field_name.go.erb
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
resource "google_binary_authorization_attestor" "<%= ctx[:primary_resource_id] %>" {
name = "<%= ctx[:vars]["attestor_name"] %>"
attestation_authority_note {
note_reference = "${google_container_analysis_note.note.name}"
public_keys {
id = "${data.google_kms_crypto_key_version.version.id}"
pkix_public_key {
public_key_pem = "${data.google_kms_crypto_key_version.version.public_key[0].pem}"
signature_algorithm = "${data.google_kms_crypto_key_version.version.public_key[0].algorithm}"
}
}
}
}

data "google_kms_crypto_key_version" "version" {
crypto_key = "${google_kms_crypto_key.crypto-key.self_link}"
}

resource "google_container_analysis_note" "note" {
name = "<%= ctx[:vars]["note_name"] %>"
attestation_authority {
hint {
human_readable_name = "Attestor Note"
}
}
}

resource "google_kms_crypto_key" "crypto-key" {
name = "<%= ctx[:vars]["key_name"] %>"
key_ring = "${google_kms_key_ring.keyring.self_link}"
purpose = "ASYMMETRIC_SIGN"

version_template {
algorithm = "RSA_SIGN_PKCS1_4096_SHA512"
}

lifecycle {
prevent_destroy = true
}
}

resource "google_kms_key_ring" "keyring" {
name = "<%= ctx[:vars]["keyring_name"] %>"
location = "global"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package google

import (
"fmt"
"log"
"strconv"
"strings"

"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceGoogleKmsCryptoKeyVersion() *schema.Resource {
return &schema.Resource{
Read: dataSourceGoogleKmsCryptoKeyVersionRead,
Schema: map[string]*schema.Schema{
"crypto_key": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"version": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
},
"algorithm": {
Type: schema.TypeString,
Computed: true,
},
"protection_level": {
Type: schema.TypeString,
Computed: true,
},
"state": {
Type: schema.TypeString,
Computed: true,
},
"public_key": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"algorithm": {
Type: schema.TypeString,
Computed: true,
},
"pem": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
}
}

func dataSourceGoogleKmsCryptoKeyVersionRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

url, err := replaceVars(d, config, "{{KmsBasePath}}{{crypto_key}}/cryptoKeyVersions/{{version}}")
if err != nil {
return err
}

log.Printf("[DEBUG] Getting attributes for CryptoKeyVersion: %#v", url)
res, err := sendRequest(config, "GET", url, nil)
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("KmsCryptoKeyVersion %q", d.Id()))
}

if err := d.Set("version", flattenKmsCryptoKeyVersionVersion(res["name"], d)); err != nil {
return fmt.Errorf("Error reading CryptoKeyVersion: %s", err)
}
if err := d.Set("state", flattenKmsCryptoKeyVersionState(res["state"], d)); err != nil {
return fmt.Errorf("Error reading CryptoKeyVersion: %s", err)
}
if err := d.Set("protection_level", flattenKmsCryptoKeyVersionProtectionLevel(res["protectionLevel"], d)); err != nil {
return fmt.Errorf("Error reading CryptoKeyVersion: %s", err)
}
if err := d.Set("algorithm", flattenKmsCryptoKeyVersionAlgorithm(res["algorithm"], d)); err != nil {
return fmt.Errorf("Error reading CryptoKeyVersion: %s", err)
}

url, err = replaceVars(d, config, "{{KmsBasePath}}{{crypto_key}}")
if err != nil {
return err
}

log.Printf("[DEBUG] Getting purpose of CryptoKey: %#v", url)
res, err = sendRequest(config, "GET", url, nil)
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("KmsCryptoKey %q", d.Id()))
}

if res["purpose"] == "ASYMMETRIC_SIGN" || res["purpose"] == "ASYMMETRIC_DECRYPT" {
url, err = replaceVars(d, config, "{{KmsBasePath}}{{crypto_key}}/cryptoKeyVersions/{{version}}/publicKey")
if err != nil {
return err
}
log.Printf("[DEBUG] Getting public key of CryptoKeyVersion: %#v", url)
res, _ = sendRequest(config, "GET", url, nil)

if err := d.Set("public_key", flattenKmsCryptoKeyVersionPublicKey(res, d)); err != nil {
return fmt.Errorf("Error reading CryptoKeyVersion public key: %s", err)
}
}
d.SetId(fmt.Sprintf("//cloudkms.googleapis.com/%s/cryptoKeyVersions/%d", d.Get("crypto_key"), d.Get("version")))

return nil
}

func flattenKmsCryptoKeyVersionVersion(v interface{}, d *schema.ResourceData) interface{} {
parts := strings.Split(v.(string), "/")
version := parts[len(parts)-1]
// Handles the string fixed64 format
if intVal, err := strconv.ParseInt(version, 10, 64); err == nil {
return intVal
} // let terraform core handle it if we can't convert the string to an int.
return v
}

func flattenKmsCryptoKeyVersionState(v interface{}, d *schema.ResourceData) interface{} {
return v
}

func flattenKmsCryptoKeyVersionProtectionLevel(v interface{}, d *schema.ResourceData) interface{} {
return v
}

func flattenKmsCryptoKeyVersionAlgorithm(v interface{}, d *schema.ResourceData) interface{} {
return v
}

func flattenKmsCryptoKeyVersionPublicKey(v interface{}, d *schema.ResourceData) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["pem"] =
flattenKmsCryptoKeyVersionPublicKeyPem(original["pem"], d)
transformed["algorithm"] =
flattenKmsCryptoKeyVersionPublicKeyAlgorithm(original["algorithm"], d)
return []interface{}{transformed}
}
func flattenKmsCryptoKeyVersionPublicKeyPem(v interface{}, d *schema.ResourceData) interface{} {
return v
}

func flattenKmsCryptoKeyVersionPublicKeyAlgorithm(v interface{}, d *schema.ResourceData) interface{} {
return v
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package google

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/resource"
)

func TestAccDataSourceGoogleKmsCryptoKeyVersion_basic(t *testing.T) {
asymSignKey := BootstrapKMSKeyWithPurpose(t, "ASYMMETRIC_SIGN")
asymDecrKey := BootstrapKMSKeyWithPurpose(t, "ASYMMETRIC_DECRYPT")
symKey := BootstrapKMSKey(t)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceGoogleKmsCryptoKeyVersion_basic(asymSignKey.CryptoKey.Name),
Check: resource.TestCheckResourceAttr("data.google_kms_crypto_key_version.version", "version", "1"),
},
// Asymmetric keys should have a public key
{
Config: testAccDataSourceGoogleKmsCryptoKeyVersion_basic(asymSignKey.CryptoKey.Name),
Check: resource.TestCheckResourceAttr("data.google_kms_crypto_key_version.version", "public_key.#", "1"),
},
{
Config: testAccDataSourceGoogleKmsCryptoKeyVersion_basic(asymDecrKey.CryptoKey.Name),
Check: resource.TestCheckResourceAttr("data.google_kms_crypto_key_version.version", "public_key.#", "1"),
},
// Symmetric key should have no public key
{
Config: testAccDataSourceGoogleKmsCryptoKeyVersion_basic(symKey.CryptoKey.Name),
Check: resource.TestCheckResourceAttr("data.google_kms_crypto_key_version.version", "public_key.#", "0"),
},
},
})
}

func testAccDataSourceGoogleKmsCryptoKeyVersion_basic(kmsKey string) string {
return fmt.Sprintf(`
data "google_kms_crypto_key_version" "version" {
crypto_key = "%s"
}
`, kmsKey)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,29 @@ func TestAccBinaryAuthorizationAttestor_full(t *testing.T) {
})
}

func TestAccBinaryAuthorizationAttestor_kms(t *testing.T) {
t.Parallel()

kms := BootstrapKMSKeyWithPurpose(t, "ASYMMETRIC_SIGN")
attestorName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckBinaryAuthorizationAttestorDestroy,
Steps: []resource.TestStep{
{
Config: testAccBinaryAuthorizationAttestorKms(attestorName, kms.CryptoKey.Name),
},
{
ResourceName: "google_binary_authorization_attestor.attestor",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccBinaryAuthorizationAttestor_update(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -183,6 +206,35 @@ MAU9vdm1DIv567meMqTaVZgR3w7bck2P49AO8lO5ERFpVkErtu/98y+rUy9d789l
+OPuS1NGnxI1YKsNaWJF4uJVuvQuZ1twrhCbGNtVorO2U12+cEq+YtUxj7kmdOC1
qoIRW6y0+UlAc+MbqfL0ziHDOAmcqz1GnROg
=6Bvm`
func testAccBinaryAuthorizationAttestorKms(attestorName, kmsKey string) string {
return fmt.Sprintf(`data "google_kms_crypto_key_version" "version" {
crypto_key = "%s"
}
resource "google_container_analysis_note" "note" {
name = "%s"
attestation_authority {
hint {
human_readable_name = "My Attestor"
}
}
}
resource "google_binary_authorization_attestor" "attestor" {
name = "%s"
attestation_authority_note {
note_reference = "${google_container_analysis_note.note.name}"
public_keys {
id = "${data.google_kms_crypto_key_version.version.id}"
pkix_public_key {
public_key_pem = "${data.google_kms_crypto_key_version.version.public_key[0].pem}"
signature_algorithm = "${data.google_kms_crypto_key_version.version.public_key[0].algorithm}"
}
}
}
}`, kmsKey, attestorName, attestorName)
}
<% else %>
// Because Container Analysis is still in beta, we can't run any of the tests that call that
// resource without vendoring in the full beta provider.
Expand Down
Loading

0 comments on commit 1fd396d

Please sign in to comment.