Skip to content

Commit

Permalink
Support IPv6 listen address
Browse files Browse the repository at this point in the history
Clarify comments to separate `Config` into:
- `Address` - the IP address the server binds to, probably 0.0.0.0
- `Hostname` - the hostname/IP that clients should use

Also fix both so they handle IPv6 literals correctly.  Fixes #339
  • Loading branch information
anguslees committed Nov 17, 2020
1 parent 150fec1 commit 8a6aa47
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 13 deletions.
15 changes: 13 additions & 2 deletions pkg/config/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,19 @@ func (c *Config) selfSignCertificate() ([]byte, []byte, error) {
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
DNSNames: []string{c.Hostname},
IPAddresses: []net.IP{net.ParseIP(c.Address)},
}
addrIP := net.ParseIP(c.Address)
if !addrIP.IsUnspecified() {
template.IPAddresses = append(template.IPAddresses, addrIP)
}
if ip := net.ParseIP(c.Hostname); ip != nil {
// is an IP literal
if !addrIP.Equal(ip) {
template.IPAddresses = append(template.IPAddresses, ip)
}
} else {
// is a hostname (not an IP literal)
template.DNSNames = append(template.DNSNames, c.Hostname)
}

certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
Expand Down
104 changes: 104 additions & 0 deletions pkg/config/certs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package config

import (
"bytes"
"crypto/x509"
"net"
"sort"
"testing"
)

func ipsEqual(a, b []net.IP) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !a[i].Equal(b[i]) {
return false
}
}
return true
}

func stringsEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

func TestSelfSignCert(t *testing.T) {
tests := []struct {
config Config
err error
dnsNames []string
ips []net.IP
}{
{
config: Config{
Address: "127.0.0.1",
Hostname: "127.0.0.1",
},
dnsNames: []string{},
ips: []net.IP{net.IPv4(127, 0, 0, 1)},
},
{
config: Config{
Address: "192.0.2.1",
Hostname: "example.com",
},
dnsNames: []string{"example.com"},
ips: []net.IP{net.IPv4(192, 0, 2, 1)},
},
{
config: Config{
Address: "::",
Hostname: "2001:db8::1:0",
},
dnsNames: []string{},
ips: []net.IP{net.ParseIP("2001:db8::1:0")},
},
}

for _, test := range tests {
certBytes, keyBytes, err := test.config.selfSignCertificate()
if err != nil {
if err != test.err {
t.Errorf("Expected error %v, got %v", test.err, err)
}
continue
}

cert, err := x509.ParseCertificate(certBytes)
if err != nil {
t.Fatalf("ParseCertificate: %v", err)
}

key, err := x509.ParsePKCS1PrivateKey(keyBytes)
if err != nil {
t.Fatalf("ParsePKCS1PrivateKey: %v", err)
}
if err := key.Validate(); err != nil {
t.Errorf("private key is invalid: %v", err)
}

dnsNames := cert.DNSNames
sort.Strings(dnsNames)
if !stringsEqual(dnsNames, test.dnsNames) {
t.Errorf("expected DNSNames %v, got %v", test.dnsNames, dnsNames)
}

ips := cert.IPAddresses
sort.Slice(ips, func(i, j int) bool {
return bytes.Compare(ips[i].To16(), ips[j].To16()) == -1
})
if !ipsEqual(ips, test.ips) {
t.Errorf("expected IPs %v, got %v", test.ips, ips)
}
}
}
23 changes: 18 additions & 5 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,32 @@ package config

import (
"fmt"
"net"
"net/url"
"path/filepath"
"strconv"

"github.com/sirupsen/logrus"
)

// ListenURL returns the URL to listen on.
func (c *Config) ListenURL() string {
return fmt.Sprintf("https://%s/authenticate", c.ListenAddr())
// ServerURL returns the URL to connect to this server.
func (c *Config) ServerURL() string {
u := url.URL{
Scheme: "https",
Host: c.ServerAddr(),
Path: "/authenticate",
}
return u.String()
}

// ServerAddr returns the host and port clients should use for server endpoint.
func (c *Config) ServerAddr() string {
return net.JoinHostPort(c.Hostname, strconv.Itoa(c.HostPort))
}

// ListenAddr returns the IP address and port mapping to bind with
func (c *Config) ListenAddr() string {
return fmt.Sprintf("%s:%d", c.Hostname, c.HostPort)
return net.JoinHostPort(c.Address, strconv.Itoa(c.HostPort))
}

// GenerateFiles will generate the certificate+provate key
Expand All @@ -57,7 +70,7 @@ func (c *Config) CreateKubeconfig() error {
// write a kubeconfig suitable for the API server to call us
logrus.WithField("kubeconfigPath", c.GenerateKubeconfigPath).Info("writing webhook kubeconfig file")
err = kubeconfigParams{
ServerURL: c.ListenURL(),
ServerURL: c.ServerURL(),
CertificateAuthorityBase64: certToPEMBase64(cert.Certificate[0]),
}.writeTo(c.GenerateKubeconfigPath)
if err != nil {
Expand Down
41 changes: 41 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package config

import (
"testing"
)

func TestServerUrl(t *testing.T) {
tests := []struct {
config Config
expected string
}{
{
config: Config{
Hostname: "example.com",
HostPort: 6443,
},
expected: "https://example.com:6443/authenticate",
},
{
config: Config{
Hostname: "127.0.0.1",
HostPort: 8080,
},
expected: "https://127.0.0.1:8080/authenticate",
},
{
config: Config{
Hostname: "2001:db8::1:0",
HostPort: 1234,
},
expected: "https://[2001:db8::1:0]:1234/authenticate",
},
}

for _, test := range tests {
actual := test.config.ServerURL()
if actual != test.expected {
t.Errorf("Expected %q, got %q", test.expected, actual)
}
}
}
2 changes: 1 addition & 1 deletion pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type Config struct {
// HostPort is the TCP Port on which to listen for authentication checks.
HostPort int

// Hostname is the hostname that the server bind to.
// Hostname is the address clients should use for this server.
Hostname string

// GenerateKubeconfigPath is the output path where a generated webhook
Expand Down
7 changes: 2 additions & 5 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,6 @@ func New(cfg config.Config, mappers []mapper.Mapper) *Server {
logrus.WithField("accountID", account).Infof("mapping IAM Account")
}

listenAddr := fmt.Sprintf("%s:%d", c.Address, c.HostPort)
listenURL := fmt.Sprintf("https://%s/authenticate", listenAddr)

cert, err := c.GetOrCreateCertificate()
if err != nil {
logrus.WithError(err).Fatalf("could not load/generate a certificate")
Expand All @@ -126,7 +123,7 @@ func New(cfg config.Config, mappers []mapper.Mapper) *Server {
}

// start a TLS listener with our custom certs
listener, err := tls.Listen("tcp", listenAddr, &tls.Config{
listener, err := tls.Listen("tcp", c.ListenAddr(), &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{*cert},
})
Expand All @@ -138,7 +135,7 @@ func New(cfg config.Config, mappers []mapper.Mapper) *Server {
errLog := logrus.WithField("http", "error").Writer()
defer errLog.Close()

logrus.Infof("listening on %s", listenURL)
logrus.Infof("listening on %s", listener.Addr())
logrus.Infof("reconfigure your apiserver with `--authentication-token-webhook-config-file=%s` to enable (assuming default hostPath mounts)", c.GenerateKubeconfigPath)
c.httpServer = http.Server{
ErrorLog: log.New(errLog, "", 0),
Expand Down

0 comments on commit 8a6aa47

Please sign in to comment.