-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from aspectcapital/certificates
Add artifactory_certificate resource type
- Loading branch information
Showing
5 changed files
with
354 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
package artifactory | ||
|
||
import ( | ||
"context" | ||
"crypto/sha256" | ||
"crypto/x509" | ||
"encoding/hex" | ||
"encoding/pem" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/atlassian/go-artifactory/v2/artifactory" | ||
v1 "github.com/atlassian/go-artifactory/v2/artifactory/v1" | ||
"github.com/hashicorp/terraform/helper/schema" | ||
) | ||
|
||
func resourceArtifactoryCertificate() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceCertificateCreate, | ||
Read: resourceCertificateRead, | ||
Update: resourceCertificateUpdate, | ||
Delete: resourceCertificateDelete, | ||
Exists: resourceCertificateExists, | ||
|
||
Importer: &schema.ResourceImporter{ | ||
State: schema.ImportStatePassthrough, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"alias": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"content": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Sensitive: true, | ||
}, | ||
"fingerprint": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"issued_by": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"issued_on": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"issued_to": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"valid_until": { | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
}, | ||
|
||
CustomizeDiff: func(d *schema.ResourceDiff, v interface{}) error { | ||
fingerprint, err := calculateFingerPrint(d.Get("content").(string)) | ||
if err != nil { | ||
return err | ||
} | ||
if d.Get("fingerprint").(string) != fingerprint { | ||
if err := d.SetNewComputed("fingerprint"); err != nil { | ||
fmt.Println(err) | ||
return err | ||
} | ||
} | ||
return nil | ||
}, | ||
} | ||
} | ||
|
||
func formatFingerPrint(f []byte) string { | ||
buf := make([]byte, 0, 3*len(f)) | ||
x := buf[1*len(f) : 3*len(f)] | ||
hex.Encode(x, f) | ||
for i := 0; i < len(x); i += 2 { | ||
buf = append(buf, x[i], x[i+1], ':') | ||
} | ||
return strings.ToUpper(string(buf[:len(buf)-1])) | ||
} | ||
|
||
func extractCertificate(pemData string) (*x509.Certificate, error) { | ||
block, rest := pem.Decode([]byte(pemData)) | ||
for block != nil { | ||
if block.Type != "CERTIFICATE" { | ||
block, rest = pem.Decode(rest) | ||
continue | ||
} | ||
|
||
cert, err := x509.ParseCertificate(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return cert, nil | ||
} | ||
|
||
return nil, fmt.Errorf("no certificate in PEM data") | ||
} | ||
|
||
func calculateFingerPrint(pemData string) (string, error) { | ||
cert, err := extractCertificate(pemData) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
fingerprint := sha256.Sum256(cert.Raw) | ||
|
||
return formatFingerPrint(fingerprint[:]), nil | ||
} | ||
|
||
func findCertificate(d *schema.ResourceData, m interface{}) (*v1.CertificateDetails, error) { | ||
c := m.(*artifactory.Artifactory) | ||
|
||
certs, _, err := c.V1.Security.GetCertificates(context.Background()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// No way other than to loop through each certificate | ||
for _, cert := range *certs { | ||
if *cert.CertificateAlias == d.Id() { | ||
return &cert, nil | ||
} | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
func resourceCertificateCreate(d *schema.ResourceData, m interface{}) error { | ||
d.SetId(d.Get("alias").(string)) | ||
return resourceCertificateUpdate(d, m) | ||
} | ||
|
||
func resourceCertificateRead(d *schema.ResourceData, m interface{}) error { | ||
cert, err := findCertificate(d, m) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if cert != nil { | ||
hasErr := false | ||
logErr := cascadingErr(&hasErr) | ||
|
||
logErr(d.Set("alias", *cert.CertificateAlias)) | ||
logErr(d.Set("fingerprint", *cert.FingerPrint)) | ||
logErr(d.Set("issued_by", *cert.IssuedBy)) | ||
logErr(d.Set("issued_on", *cert.IssuedOn)) | ||
logErr(d.Set("issued_to", *cert.IssuedTo)) | ||
logErr(d.Set("valid_until", *cert.ValidUntil)) | ||
|
||
if hasErr { | ||
return fmt.Errorf("failed to pack certificate") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
d.SetId("") | ||
|
||
return nil | ||
} | ||
|
||
func resourceCertificateUpdate(d *schema.ResourceData, m interface{}) error { | ||
c := m.(*artifactory.Artifactory) | ||
|
||
_, _, err := c.V1.Security.AddCertificate(context.Background(), d.Id(), strings.NewReader(d.Get("content").(string))) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return resourceCertificateRead(d, m) | ||
} | ||
|
||
func resourceCertificateDelete(d *schema.ResourceData, m interface{}) error { | ||
c := m.(*artifactory.Artifactory) | ||
|
||
_, _, err := c.V1.Security.DeleteCertificate(context.Background(), d.Id()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
d.SetId("") | ||
|
||
return nil | ||
} | ||
|
||
func resourceCertificateExists(d *schema.ResourceData, m interface{}) (bool, error) { | ||
cert, err := findCertificate(d, m) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
if cert != nil { | ||
return true, nil | ||
} | ||
|
||
return false, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package artifactory | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/atlassian/go-artifactory/v2/artifactory" | ||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/terraform" | ||
) | ||
|
||
const certificateFull = ` | ||
resource "artifactory_certificate" "foobar" { | ||
alias = "foobar" | ||
content = <<EOF | ||
-----BEGIN CERTIFICATE----- | ||
MIICUjCCAbugAwIBAgIJALRDng3rGeQvMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV | ||
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg | ||
Q29tcGFueSBMdGQwHhcNMTkwNTE3MTAwMzI2WhcNMjkwNTE0MTAwMzI2WjBCMQsw | ||
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh | ||
dWx0IENvbXBhbnkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVBRt7 | ||
Ua3j7K2htVRu1tw629ZZZQI35RGm/53ffF/QUUFXk35at+IiwYZGGQbOGuN1pdji | ||
gki9/Qit/WO/3uadSkGelKOUYD0DIemlhcZt6iPMQq8mYlUkMPZz5Qlj0ldKI3g+ | ||
Q8Tc/6vEeBv/9jrm9Efg/uwc0DjD8B4Ny6xMHQIDAQABo1AwTjAdBgNVHQ4EFgQU | ||
VrBaHnYLayO2lKIUde8etG0H6owwHwYDVR0jBBgwFoAUVrBaHnYLayO2lKIUde8e | ||
tG0H6owwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQA4VBFCrbuOsKtY | ||
uNlSQCBkTXg907iXihZ+Of/2rerS2gfDCUHdz0xbYdlttNjoGVCA+0alt7ugfYpl | ||
fy5aAfCHLXEgYrlhe6oDtCMSskbkKFTEI/bRqwGMDb+9NO/yh2KLbNueKJz9Vs5V | ||
GV9pUrgW6c7kLrC9vpHP+47iyQEbnw== | ||
-----END CERTIFICATE----- | ||
-----BEGIN PRIVATE KEY----- | ||
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANUFG3tRrePsraG1 | ||
VG7W3Drb1lllAjflEab/nd98X9BRQVeTflq34iLBhkYZBs4a43Wl2OKCSL39CK39 | ||
Y7/e5p1KQZ6Uo5RgPQMh6aWFxm3qI8xCryZiVSQw9nPlCWPSV0ojeD5DxNz/q8R4 | ||
G//2Oub0R+D+7BzQOMPwHg3LrEwdAgMBAAECgYAxWA6GoWQDcRbDZ6qYRkMbi0L6 | ||
0DAUXIabRYj/dOMI8VmOfMb/IqtKW8PLxw5Rfd8EqJc12PIauFtjWlfZ4TtP9erQ | ||
1imw2SpVMAWt4HLUw7oONKgNMnBtVQBCoXLuXcnJbCxeRiV1oJtvrddUJPOtUc+y | ||
t5gGTyx/zUAXzPzT7QJBAOvu4CH0Xc+1GdXFUFLzF8B3SFwnOFRERJxFq43dw4t3 | ||
tXcON/UyegYcQz2JqKcofwRhM4+uXGnWE+9oOOnxL8sCQQDnI1QtMv+tZcqIcmk6 | ||
1ykyNa530eCfoqAvVTRwPIsAD/DZLC4HJNSQauPXC4Unt1tqmOmUoZmgzYQlVsGO | ||
ISa3AkB2xWpPrZUMWz8GPq6RE4+BdIsY2SWiRjvD787NPDaUn07bAG1rIl4LdW7k | ||
K8ibXeeTbNtoGX6sSPkALJd6LdDBAkEA5FAhdgRKSh2iUeWxzE18g/xCuli2aPlb | ||
AWZIxhUHuKgGYH8jeCsJTR5IsMLQZMrZohIpqId4GT7oqXlo99wHQQJBAOvX+5z6 | ||
iCooatRyMnwUV6sJ225ZawuJ4sXFt6CA7aOZQ+G5zvG694ONxG9qeF2YnySQp1HH | ||
V87CqqFaUigTzmI= | ||
-----END PRIVATE KEY----- | ||
EOF | ||
}` | ||
|
||
func TestAccCertificate_full(t *testing.T) { | ||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
CheckDestroy: testAccCheckUserDestroy("artifactory_certificate.foobar"), | ||
Providers: testAccProviders, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: certificateFull, | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("artifactory_certificate.foobar", "alias", "foobar"), | ||
resource.TestCheckResourceAttr("artifactory_certificate.foobar", "fingerprint", "ED:67:0B:D2:84:C2:93:6D:56:6F:A7:4D:5A:CC:B7:AF:8A:C0:1D:2A:7C:F3:4A:57:31:83:22:30:44:5F:63:9D"), | ||
resource.TestCheckResourceAttr("artifactory_certificate.foobar", "issued_by", "Unknown"), | ||
resource.TestCheckResourceAttr("artifactory_certificate.foobar", "issued_on", "2019-05-17T11:03:26.000+01:00"), | ||
resource.TestCheckResourceAttr("artifactory_certificate.foobar", "issued_to", "Unknown"), | ||
resource.TestCheckResourceAttr("artifactory_certificate.foobar", "valid_until", "2029-05-14T11:03:26.000+01:00"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCheckCertificateDestroy(id string) func(*terraform.State) error { | ||
return func(s *terraform.State) error { | ||
client := testAccProvider.Meta().(*artifactory.Artifactory) | ||
rs, ok := s.RootModule().Resources[id] | ||
|
||
if !ok { | ||
return fmt.Errorf("err: Resource id[%s] not found", id) | ||
} | ||
|
||
certs, _, err := client.V1.Security.GetCertificates(context.Background()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, cert := range *certs { | ||
if *cert.CertificateAlias == rs.Primary.ID { | ||
return fmt.Errorf("error: Certificate %s still exists", rs.Primary.ID) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
--- | ||
layout: "artifactory" | ||
page_title: "Artifactory: artifactory_certificate" | ||
sidebar_current: "docs-artifactory-resource-certificate" | ||
description: |- | ||
Provides a certificate resource. | ||
--- | ||
|
||
# artifactory_certificate | ||
|
||
Provides an Artifactory certificate resource. This can be used to create and manage Artifactory certificates which can be used as client authentication against remote repositories. | ||
|
||
## Example Usage | ||
|
||
```hcl | ||
# Create a new Artifactory certificate called my-cert | ||
resource "artifactory_certificate" "my-cert" { | ||
alias = "my-cert" | ||
content = "${file("/path/to/bundle.pem")}" | ||
} | ||
# This can then be used by a remote repository | ||
resource "artifactory_remote_repository" "my-remote" { | ||
... | ||
client_tls_certificate = "${artifactory_certificate.my-cert.alias}" | ||
... | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
The following arguments are supported: | ||
|
||
* `alias` - (Required) Name of certificate. | ||
* `content` - (Required) PEM-encoded client certificate and private key. | ||
|
||
## Attribute Reference | ||
|
||
In addition to all arguments above, the following attributes are exported: | ||
|
||
* `fingerprint` - SHA256 fingerprint of the certificate. | ||
* `issued_by` - Name of the certificate authority that issued the certificate. | ||
* `issued_on` - The time & date when the certificate is valid from. | ||
* `issued_to` - Name of whom the certificate has been issued to. | ||
* `valid_until` - The time & date when the certificate expires. | ||
|
||
## Import | ||
|
||
Certificates can be imported using their alias, e.g. | ||
|
||
``` | ||
$ terraform import artifactory_certificate.my-cert my-cert | ||
``` |