diff --git a/acme/api/linker.go b/acme/api/linker.go index d4490470..a605ffc3 100644 --- a/acme/api/linker.go +++ b/acme/api/linker.go @@ -3,13 +3,29 @@ package api import ( "context" "fmt" + "net" "net/url" + "strings" "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} } diff --git a/acme/api/linker_test.go b/acme/api/linker_test.go index 4790dec8..74c2c8b0 100644 --- a/acme/api/linker_test.go +++ b/acme/api/linker_test.go @@ -31,6 +31,86 @@ func TestLinker_GetUnescapedPathSuffix(t *testing.T) { assert.Equals(t, getPath(CertificateLinkType, "{provisionerID}", "{certID}"), "/{provisionerID}/certificate/{certID}") } +func TestLinker_DNS(t *testing.T) { + prov := newProv() + escProvName := url.PathEscape(prov.GetName()) + ctx := context.WithValue(context.Background(), provisionerContextKey, prov) + type test struct { + name string + dns string + prefix string + expectedDirectoryLink string + } + tests := []test{ + { + name: "domain", + dns: "ca.smallstep.com", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://ca.smallstep.com/acme/%s/directory", escProvName), + }, + { + name: "domain-port", + dns: "ca.smallstep.com:8443", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://ca.smallstep.com:8443/acme/%s/directory", escProvName), + }, + { + name: "ipv4", + dns: "127.0.0.1", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://127.0.0.1/acme/%s/directory", escProvName), + }, + { + name: "ipv4-port", + dns: "127.0.0.1:8443", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://127.0.0.1:8443/acme/%s/directory", escProvName), + }, + { + name: "ipv6", + dns: "[::1]", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://[::1]/acme/%s/directory", escProvName), + }, + { + name: "ipv6-port", + dns: "[::1]:8443", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://[::1]:8443/acme/%s/directory", escProvName), + }, + { + name: "ipv6-no-brackets", + dns: "::1", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://[::1]/acme/%s/directory", escProvName), + }, + { + name: "ipv6-port-no-brackets", + dns: "::1:8443", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://[::1]:8443/acme/%s/directory", escProvName), + }, + { + name: "ipv6-long-no-brackets", + dns: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]/acme/%s/directory", escProvName), + }, + { + name: "ipv6-long-port-no-brackets", + dns: "2001:0db8:85a3:0000:0000:8a2e:0370:7334:8443", + prefix: "acme", + expectedDirectoryLink: fmt.Sprintf("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8443/acme/%s/directory", escProvName), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + linker := NewLinker(tt.dns, tt.prefix) + assert.Equals(t, tt.expectedDirectoryLink, linker.GetLink(ctx, DirectoryLinkType)) + }) + } +} + func TestLinker_GetLink(t *testing.T) { dns := "ca.smallstep.com" prefix := "acme"