mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-14 06:57:31 +00:00
cmd/containerboot: load containerboot serve config that does not contain HTTPS endpoint in tailnets with HTTPS disabled (#14538)
cmd/containerboot: load containerboot serve config that does not contain HTTPS endpoint in tailnets with HTTPS disabled Fixes an issue where, if a tailnet has HTTPS disabled, no serve config set via TS_SERVE_CONFIG was loaded, even if it does not contain an HTTPS endpoint. Now for tailnets with HTTPS disabled serve config provided to containerboot is considered invalid (and therefore not loaded) only if there is an HTTPS endpoint defined in the config. Fixes tailscale/tailscale#14495 Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
267
cmd/containerboot/serve_test.go
Normal file
267
cmd/containerboot/serve_test.go
Normal file
@@ -0,0 +1,267 @@
|
||||
// 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/tailscale"
|
||||
"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 {
|
||||
*tailscale.LocalClient
|
||||
setServeCalled bool
|
||||
}
|
||||
|
||||
func (m *fakeLocalClient) SetServeConfig(ctx context.Context, cfg *ipn.ServeConfig) error {
|
||||
m.setServeCalled = true
|
||||
return 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user