smallstep-certificates/acme/api/linker.go

198 lines
5.9 KiB
Go
Raw Normal View History

2021-03-05 07:10:46 +00:00
package api
import (
"context"
"fmt"
"net"
2021-03-05 07:10:46 +00:00
"net/url"
"strings"
2021-03-05 07:10:46 +00:00
"github.com/smallstep/certificates/acme"
)
// NewLinker returns a new Directory type.
func NewLinker(dns, prefix string) Linker {
_, _, err := net.SplitHostPort(dns)
if err != nil && strings.Contains(err.Error(), "too many colons in address") {
// this is most probably an IPv6 without brackets, e.g. ::1, 2001:0db8:85a3:0000:0000:8a2e:0370:7334
// in case a port was appended to this wrong format, we try to extract the port, then check if it's
// still a valid IPv6: 2001:0db8:85a3:0000:0000:8a2e:0370:7334:8443 (8443 is the port). If none of
// these cases, then the input dns is not changed.
lastIndex := strings.LastIndex(dns, ":")
hostPart, portPart := dns[:lastIndex], dns[lastIndex+1:]
if ip := net.ParseIP(hostPart); ip != nil {
dns = "[" + hostPart + "]:" + portPart
} else if ip := net.ParseIP(dns); ip != nil {
dns = "[" + dns + "]"
}
}
return &linker{prefix: prefix, dns: dns}
2021-03-05 07:10:46 +00:00
}
// Linker interface for generating links for ACME resources.
type Linker interface {
GetLink(ctx context.Context, typ LinkType, inputs ...string) string
GetUnescapedPathSuffix(typ LinkType, provName string, inputs ...string) string
LinkOrder(ctx context.Context, o *acme.Order)
LinkAccount(ctx context.Context, o *acme.Account)
2021-03-25 07:23:57 +00:00
LinkChallenge(ctx context.Context, o *acme.Challenge, azID string)
LinkAuthorization(ctx context.Context, o *acme.Authorization)
LinkOrdersByAccountID(ctx context.Context, orders []string)
}
// linker generates ACME links.
type linker struct {
prefix string
dns string
2021-03-05 07:10:46 +00:00
}
func (l *linker) GetUnescapedPathSuffix(typ LinkType, provisionerName string, inputs ...string) string {
switch typ {
case NewNonceLinkType, NewAccountLinkType, NewOrderLinkType, NewAuthzLinkType, DirectoryLinkType, KeyChangeLinkType, RevokeCertLinkType:
return fmt.Sprintf("/%s/%s", provisionerName, typ)
case AccountLinkType, OrderLinkType, AuthzLinkType, CertificateLinkType:
return fmt.Sprintf("/%s/%s/%s", provisionerName, typ, inputs[0])
case ChallengeLinkType:
return fmt.Sprintf("/%s/%s/%s/%s", provisionerName, typ, inputs[0], inputs[1])
case OrdersByAccountLinkType:
return fmt.Sprintf("/%s/%s/%s/orders", provisionerName, AccountLinkType, inputs[0])
case FinalizeLinkType:
return fmt.Sprintf("/%s/%s/%s/finalize", provisionerName, OrderLinkType, inputs[0])
default:
return ""
}
}
2021-03-05 07:10:46 +00:00
// GetLink is a helper for GetLinkExplicit
func (l *linker) GetLink(ctx context.Context, typ LinkType, inputs ...string) string {
var (
provName string
baseURL = baseURLFromContext(ctx)
u = url.URL{}
)
2021-03-05 07:10:46 +00:00
if p, err := provisionerFromContext(ctx); err == nil && p != nil {
provName = p.GetName()
}
// Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351
if baseURL != nil {
u = *baseURL
}
u.Path = l.GetUnescapedPathSuffix(typ, provName, inputs...)
2021-03-05 07:10:46 +00:00
// If no Scheme is set, then default to https.
if u.Scheme == "" {
u.Scheme = "https"
}
2021-03-05 07:10:46 +00:00
// If no Host is set, then use the default (first DNS attr in the ca.json).
if u.Host == "" {
u.Host = l.dns
2021-03-05 07:10:46 +00:00
}
u.Path = l.prefix + u.Path
return u.String()
2021-03-05 07:10:46 +00:00
}
// LinkType captures the link type.
type LinkType int
const (
// NewNonceLinkType new-nonce
NewNonceLinkType LinkType = iota
// NewAccountLinkType new-account
NewAccountLinkType
// AccountLinkType account
AccountLinkType
// OrderLinkType order
OrderLinkType
// NewOrderLinkType new-order
NewOrderLinkType
// OrdersByAccountLinkType list of orders owned by account
OrdersByAccountLinkType
// FinalizeLinkType finalize order
FinalizeLinkType
// NewAuthzLinkType authz
NewAuthzLinkType
// AuthzLinkType new-authz
AuthzLinkType
// ChallengeLinkType challenge
ChallengeLinkType
// CertificateLinkType certificate
CertificateLinkType
// DirectoryLinkType directory
DirectoryLinkType
// RevokeCertLinkType revoke certificate
RevokeCertLinkType
// KeyChangeLinkType key rollover
KeyChangeLinkType
)
func (l LinkType) String() string {
switch l {
case NewNonceLinkType:
return "new-nonce"
case NewAccountLinkType:
return "new-account"
case AccountLinkType:
return "account"
case NewOrderLinkType:
return "new-order"
case OrderLinkType:
return "order"
case NewAuthzLinkType:
return "new-authz"
case AuthzLinkType:
return "authz"
case ChallengeLinkType:
return "challenge"
case CertificateLinkType:
return "certificate"
case DirectoryLinkType:
return "directory"
case RevokeCertLinkType:
return "revoke-cert"
case KeyChangeLinkType:
return "key-change"
default:
return fmt.Sprintf("unexpected LinkType '%d'", int(l))
}
}
// LinkOrder sets the ACME links required by an ACME order.
func (l *linker) LinkOrder(ctx context.Context, o *acme.Order) {
2021-03-05 07:14:56 +00:00
o.AuthorizationURLs = make([]string, len(o.AuthorizationIDs))
for i, azID := range o.AuthorizationIDs {
o.AuthorizationURLs[i] = l.GetLink(ctx, AuthzLinkType, azID)
2021-03-05 07:10:46 +00:00
}
o.FinalizeURL = l.GetLink(ctx, FinalizeLinkType, o.ID)
2021-03-05 07:10:46 +00:00
if o.CertificateID != "" {
o.CertificateURL = l.GetLink(ctx, CertificateLinkType, o.CertificateID)
2021-03-05 07:10:46 +00:00
}
}
// LinkAccount sets the ACME links required by an ACME account.
func (l *linker) LinkAccount(ctx context.Context, acc *acme.Account) {
acc.OrdersURL = l.GetLink(ctx, OrdersByAccountLinkType, acc.ID)
2021-03-05 07:10:46 +00:00
}
2021-03-06 21:06:43 +00:00
// LinkChallenge sets the ACME links required by an ACME challenge.
2021-03-25 07:23:57 +00:00
func (l *linker) LinkChallenge(ctx context.Context, ch *acme.Challenge, azID string) {
ch.URL = l.GetLink(ctx, ChallengeLinkType, azID, ch.ID)
2021-03-05 07:10:46 +00:00
}
2021-03-06 21:06:43 +00:00
// LinkAuthorization sets the ACME links required by an ACME authorization.
func (l *linker) LinkAuthorization(ctx context.Context, az *acme.Authorization) {
2021-03-05 07:10:46 +00:00
for _, ch := range az.Challenges {
2021-03-25 07:23:57 +00:00
l.LinkChallenge(ctx, ch, az.ID)
2021-03-05 07:10:46 +00:00
}
}
2021-03-06 21:06:43 +00:00
// LinkOrdersByAccountID converts each order ID to an ACME link.
func (l *linker) LinkOrdersByAccountID(ctx context.Context, orders []string) {
2021-03-06 21:06:43 +00:00
for i, id := range orders {
orders[i] = l.GetLink(ctx, OrderLinkType, id)
2021-03-06 21:06:43 +00:00
}
}