mirror of
https://github.com/tailscale/tailscale.git
synced 2025-03-28 12:02:23 +00:00

cmd/containerboot: manage HA Ingress TLS certs from containerboot When ran as HA Ingress node, containerboot now can determine whether it should manage TLS certs for the HA Ingress replicas and call the LocalAPI cert endpoint to ensure initial issuance and renewal of the shared TLS certs. Updates tailscale/corp#24795 Signed-off-by: Irbe Krumina <irbe@tailscale.com>
272 lines
5.4 KiB
Go
272 lines
5.4 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"tailscale.com/client/local"
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/kube/kubetypes"
|
|
)
|
|
|
|
func TestUpdateServeConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
sc *ipn.ServeConfig
|
|
certDomain string
|
|
wantCall bool
|
|
}{
|
|
{
|
|
name: "no_https_no_cert_domain",
|
|
sc: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
80: {HTTP: true},
|
|
},
|
|
},
|
|
certDomain: kubetypes.ValueNoHTTPS, // tailnet has HTTPS disabled
|
|
wantCall: true, // should set serve config as it doesn't have HTTPS endpoints
|
|
},
|
|
{
|
|
name: "https_with_cert_domain",
|
|
sc: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
443: {HTTPS: true},
|
|
},
|
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
|
"${TS_CERT_DOMAIN}:443": {
|
|
Handlers: map[string]*ipn.HTTPHandler{
|
|
"/": {Proxy: "http://10.0.1.100:8080"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
certDomain: "test-node.tailnet.ts.net",
|
|
wantCall: true,
|
|
},
|
|
{
|
|
name: "https_without_cert_domain",
|
|
sc: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
443: {HTTPS: true},
|
|
},
|
|
},
|
|
certDomain: kubetypes.ValueNoHTTPS,
|
|
wantCall: false, // incorrect configuration- should not set serve config
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
fakeLC := &fakeLocalClient{}
|
|
err := updateServeConfig(context.Background(), tt.sc, tt.certDomain, fakeLC)
|
|
if err != nil {
|
|
t.Errorf("updateServeConfig() error = %v", err)
|
|
}
|
|
if fakeLC.setServeCalled != tt.wantCall {
|
|
t.Errorf("SetServeConfig() called = %v, want %v", fakeLC.setServeCalled, tt.wantCall)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReadServeConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
gotSC string
|
|
certDomain string
|
|
wantSC *ipn.ServeConfig
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "empty_file",
|
|
},
|
|
{
|
|
name: "valid_config_with_cert_domain_placeholder",
|
|
gotSC: `{
|
|
"TCP": {
|
|
"443": {
|
|
"HTTPS": true
|
|
}
|
|
},
|
|
"Web": {
|
|
"${TS_CERT_DOMAIN}:443": {
|
|
"Handlers": {
|
|
"/api": {
|
|
"Proxy": "https://10.2.3.4/api"
|
|
}}}}}`,
|
|
certDomain: "example.com",
|
|
wantSC: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
443: {
|
|
HTTPS: true,
|
|
},
|
|
},
|
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
|
ipn.HostPort("example.com:443"): {
|
|
Handlers: map[string]*ipn.HTTPHandler{
|
|
"/api": {
|
|
Proxy: "https://10.2.3.4/api",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid_config_for_http_proxy",
|
|
gotSC: `{
|
|
"TCP": {
|
|
"80": {
|
|
"HTTP": true
|
|
}
|
|
}}`,
|
|
wantSC: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
80: {
|
|
HTTP: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "config_without_cert_domain",
|
|
gotSC: `{
|
|
"TCP": {
|
|
"443": {
|
|
"HTTPS": true
|
|
}
|
|
},
|
|
"Web": {
|
|
"localhost:443": {
|
|
"Handlers": {
|
|
"/api": {
|
|
"Proxy": "https://10.2.3.4/api"
|
|
}}}}}`,
|
|
certDomain: "",
|
|
wantErr: false,
|
|
wantSC: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
443: {
|
|
HTTPS: true,
|
|
},
|
|
},
|
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
|
ipn.HostPort("localhost:443"): {
|
|
Handlers: map[string]*ipn.HTTPHandler{
|
|
"/api": {
|
|
Proxy: "https://10.2.3.4/api",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "invalid_json",
|
|
gotSC: "invalid json",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "serve-config.json")
|
|
if err := os.WriteFile(path, []byte(tt.gotSC), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, err := readServeConfig(path, tt.certDomain)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("readServeConfig() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !cmp.Equal(got, tt.wantSC) {
|
|
t.Errorf("readServeConfig() diff (-got +want):\n%s", cmp.Diff(got, tt.wantSC))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type fakeLocalClient struct {
|
|
*local.Client
|
|
setServeCalled bool
|
|
}
|
|
|
|
func (m *fakeLocalClient) SetServeConfig(ctx context.Context, cfg *ipn.ServeConfig) error {
|
|
m.setServeCalled = true
|
|
return nil
|
|
}
|
|
|
|
func (m *fakeLocalClient) CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err error) {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
func TestHasHTTPSEndpoint(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cfg *ipn.ServeConfig
|
|
want bool
|
|
}{
|
|
{
|
|
name: "nil_config",
|
|
cfg: nil,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "empty_config",
|
|
cfg: &ipn.ServeConfig{},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "no_https_endpoints",
|
|
cfg: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
80: {
|
|
HTTPS: false,
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "has_https_endpoint",
|
|
cfg: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
443: {
|
|
HTTPS: true,
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "mixed_endpoints",
|
|
cfg: &ipn.ServeConfig{
|
|
TCP: map[uint16]*ipn.TCPPortHandler{
|
|
80: {HTTPS: false},
|
|
443: {HTTPS: true},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := hasHTTPSEndpoint(tt.cfg)
|
|
if got != tt.want {
|
|
t.Errorf("hasHTTPSEndpoint() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|