Skip to content

Commit

Permalink
Managed certificate support (#167)
Browse files Browse the repository at this point in the history
* Add support for Firewalls

Signed-off-by: Lukas Kämmerling <[email protected]>

* Update Certificate types for managed certificates

* Implement creation of managed certificates

* Check if CookieLifetime is not nil

* Fix linter warning

* Add missing RetryIssuance method

* Extract struct

* Add missing certificate status constants

* Make certificate status a pointer

* Add IsFailed method

* Apply latest API changes

Signed-off-by: Lukas Kämmerling <[email protected]>

* Add missing comment

* Fix Type field type of certificate

* Introduce CertificateUsedByRefType

Co-authored-by: Günther Eberl <[email protected]>
Co-authored-by: Lukas Kämmerling <[email protected]>
  • Loading branch information
3 people authored Mar 30, 2021
1 parent 75c3dca commit 1fa5f34
Show file tree
Hide file tree
Showing 14 changed files with 598 additions and 185 deletions.
20 changes: 20 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@
linters-settings:
errcheck:
exclude: ./.errcheck_excludes.txt
exhaustive:
default-signifies-exhaustive: true
gomodguard:
blocked:
modules:
- github.com/tj/assert:
recommendations:
- github.com/stretchr/testify/assert
reason: |
One assertion library is enough and we use testify/assert
everywhere.
- github.com/magiconair/properties:
recommendations:
- github.com/stretchr/testify/assert
reason: >
We do not currently need to parse properties files. At the same
time this module has an assert package which tends to get
imported by accident. It is therefore blocked.
misspell:
locale: "US"

Expand All @@ -12,9 +30,11 @@ linters:
- deadcode
- depguard
- errcheck
- exhaustive
- gocritic
- goimports
- golint
- gomodguard
- gosec
- gosimple
- govet
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/hetznercloud/hcloud-go

go 1.16

require github.com/google/go-cmp v0.5.2
require (
github.com/google/go-cmp v0.5.0
github.com/stretchr/testify v1.7.0
)
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2 changes: 2 additions & 0 deletions hcloud/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti

for _, a := range as {
switch a.Status {
case ActionStatusRunning:
continue
case ActionStatusSuccess:
delete(watchIDs, a.ID)
successIDs := append(successIDs, a.ID)
Expand Down
157 changes: 147 additions & 10 deletions hcloud/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,81 @@ import (
"github.com/hetznercloud/hcloud-go/hcloud/schema"
)

// CertificateType is the type of available certificate types.
type CertificateType string

// Available certificate types.
const (
CertificateTypeUploaded CertificateType = "uploaded"
CertificateTypeManaged CertificateType = "managed"
)

// CertificateStatusType is defines the type for the various managed
// certificate status.
type CertificateStatusType string

// Possible certificate status.
const (
CertificateStatusTypePending CertificateStatusType = "pending"
CertificateStatusTypeFailed CertificateStatusType = "failed"

// only in issuance
CertificateStatusTypeCompleted CertificateStatusType = "completed"

// only in renewal
CertificateStatusTypeScheduled CertificateStatusType = "scheduled"
CertificateStatusTypeUnavailable CertificateStatusType = "unavailable"
)

// CertificateUsedByRefType is the type of used by references for
// certificates.
type CertificateUsedByRefType string

// Possible users of certificates.
const (
CertificateUsedByRefTypeLoadBalancer CertificateUsedByRefType = "load_balancer"
)

// CertificateUsedByRef points to a resource that uses this certificate.
type CertificateUsedByRef struct {
ID int
Type CertificateUsedByRefType
}

// CertificateStatus indicates the status of a managed certificate.
type CertificateStatus struct {
Issuance CertificateStatusType
Renewal CertificateStatusType
Error *Error
}

// IsFailed returns true if either the Issuance or the Renewal of a certificate
// failed. In this case the FailureReason field details the nature of the
// failure.
func (st *CertificateStatus) IsFailed() bool {
return st.Issuance == CertificateStatusTypeFailed || st.Renewal == CertificateStatusTypeFailed
}

// Certificate represents an certificate in the Hetzner Cloud.
type Certificate struct {
ID int
Name string
Labels map[string]string
Type CertificateType
Certificate string
Created time.Time
NotValidBefore time.Time
NotValidAfter time.Time
DomainNames []string
Fingerprint string
Status *CertificateStatus
UsedBy []CertificateUsedByRef
}

// CertificateCreateResult is the result of creating a certificate.
type CertificateCreateResult struct {
Certificate *Certificate
Action *Action
}

// CertificateClient is a client for the Certificates API.
Expand Down Expand Up @@ -153,16 +217,36 @@ func (c *CertificateClient) AllWithOpts(ctx context.Context, opts CertificateLis
// CertificateCreateOpts specifies options for creating a new Certificate.
type CertificateCreateOpts struct {
Name string
Type CertificateType
Certificate string
PrivateKey string
Labels map[string]string
DomainNames []string
}

// Validate checks if options are valid.
func (o CertificateCreateOpts) Validate() error {
if o.Name == "" {
return errors.New("missing name")
}
switch o.Type {
case "", CertificateTypeUploaded:
return o.validateUploaded()
case CertificateTypeManaged:
return o.validateManaged()
default:
return fmt.Errorf("invalid type: %s", o.Type)
}
}

func (o CertificateCreateOpts) validateManaged() error {
if len(o.DomainNames) == 0 {
return errors.New("no domain names")
}
return nil
}

func (o CertificateCreateOpts) validateUploaded() error {
if o.Certificate == "" {
return errors.New("missing certificate")
}
Expand All @@ -172,34 +256,71 @@ func (o CertificateCreateOpts) Validate() error {
return nil
}

// Create creates a new certificate.
// Create creates a new certificate uploaded certificate.
//
// Create returns an error for certificates of any other type. Use
// CreateCertificate to create such certificates.
func (c *CertificateClient) Create(ctx context.Context, opts CertificateCreateOpts) (*Certificate, *Response, error) {
if !(opts.Type == "" || opts.Type == CertificateTypeUploaded) {
return nil, nil, fmt.Errorf("invalid certificate type: %s", opts.Type)
}
result, resp, err := c.CreateCertificate(ctx, opts)
if err != nil {
return nil, resp, err
}
return result.Certificate, resp, nil
}

// CreateCertificate creates a new certificate of any type.
func (c *CertificateClient) CreateCertificate(
ctx context.Context, opts CertificateCreateOpts,
) (CertificateCreateResult, *Response, error) {
var (
action *Action
reqBody schema.CertificateCreateRequest
)

if err := opts.Validate(); err != nil {
return nil, nil, err
return CertificateCreateResult{}, nil, err
}
reqBody := schema.CertificateCreateRequest{
Name: opts.Name,
Certificate: opts.Certificate,
PrivateKey: opts.PrivateKey,

reqBody.Name = opts.Name

switch opts.Type {
case "", CertificateTypeUploaded:
reqBody.Type = string(CertificateTypeUploaded)
reqBody.Certificate = opts.Certificate
reqBody.PrivateKey = opts.PrivateKey
case CertificateTypeManaged:
reqBody.Type = string(CertificateTypeManaged)
reqBody.DomainNames = opts.DomainNames
default:
return CertificateCreateResult{}, nil, fmt.Errorf("invalid certificate type: %v", opts.Type)
}

if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
return CertificateCreateResult{}, nil, err
}
req, err := c.client.NewRequest(ctx, "POST", "/certificates", bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
return CertificateCreateResult{}, nil, err
}

respBody := schema.CertificateCreateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
return CertificateCreateResult{}, resp, err
}
return CertificateFromSchema(respBody.Certificate), resp, nil
cert := CertificateFromSchema(respBody.Certificate)
if respBody.Action != nil {
action = ActionFromSchema(*respBody.Action)
}

return CertificateCreateResult{Certificate: cert, Action: action}, resp, nil
}

// CertificateUpdateOpts specifies options for updating a Certificate.
Expand Down Expand Up @@ -244,3 +365,19 @@ func (c *CertificateClient) Delete(ctx context.Context, certificate *Certificate
}
return c.client.Do(req, nil)
}

// RetryIssuance retries the issuance of a failed managed certificate.
func (c *CertificateClient) RetryIssuance(ctx context.Context, certificate *Certificate) (*Action, *Response, error) {
var respBody schema.CertificateIssuanceRetryResponse

req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("/certificates/%d/actions/retry", certificate.ID), nil)
if err != nil {
return nil, nil, err
}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, nil, err
}
action := ActionFromSchema(respBody.Action)
return action, resp, nil
}
Loading

0 comments on commit 1fa5f34

Please sign in to comment.