mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 21:27:31 +00:00
cmd/k8s-operator,k8s-operator: allow optionally using LE staging endpoint for Ingress (#15360)
cmd/k8s-operator,k8s-operator: allow using LE staging endpoint for Ingress Allow to optionally use LetsEncrypt staging endpoint to issue certs for Ingress/HA Ingress, so that it is easier to experiment with initial Ingress setup without hiting rate limits. Updates tailscale/corp#24795 Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
@@ -518,6 +518,60 @@ func TestIngressAdvertiseServicesConfigPreserved(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func proxyClassesForLEStagingTest() (*tsapi.ProxyClass, *tsapi.ProxyClass, *tsapi.ProxyClass) {
|
||||
pcLEStaging := &tsapi.ProxyClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "le-staging",
|
||||
Generation: 1,
|
||||
},
|
||||
Spec: tsapi.ProxyClassSpec{
|
||||
UseLetsEncryptStagingEnvironment: true,
|
||||
},
|
||||
}
|
||||
|
||||
pcLEStagingFalse := &tsapi.ProxyClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "le-staging-false",
|
||||
Generation: 1,
|
||||
},
|
||||
Spec: tsapi.ProxyClassSpec{
|
||||
UseLetsEncryptStagingEnvironment: false,
|
||||
},
|
||||
}
|
||||
|
||||
pcOther := &tsapi.ProxyClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other",
|
||||
Generation: 1,
|
||||
},
|
||||
Spec: tsapi.ProxyClassSpec{},
|
||||
}
|
||||
|
||||
return pcLEStaging, pcLEStagingFalse, pcOther
|
||||
}
|
||||
|
||||
func setProxyClassReady(t *testing.T, fc client.Client, cl *tstest.Clock, name string) *tsapi.ProxyClass {
|
||||
t.Helper()
|
||||
pc := &tsapi.ProxyClass{}
|
||||
if err := fc.Get(context.Background(), client.ObjectKey{Name: name}, pc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pc.Status = tsapi.ProxyClassStatus{
|
||||
Conditions: []metav1.Condition{{
|
||||
Type: string(tsapi.ProxyClassReady),
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonProxyClassValid,
|
||||
Message: reasonProxyClassValid,
|
||||
LastTransitionTime: metav1.Time{Time: cl.Now().Truncate(time.Second)},
|
||||
ObservedGeneration: pc.Generation,
|
||||
}},
|
||||
}
|
||||
if err := fc.Status().Update(context.Background(), pc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return pc
|
||||
}
|
||||
|
||||
func verifyProxyGroupCounts(t *testing.T, r *ProxyGroupReconciler, wantIngress, wantEgress int) {
|
||||
t.Helper()
|
||||
if r.ingressProxyGroups.Len() != wantIngress {
|
||||
@@ -541,6 +595,16 @@ func verifyEnvVar(t *testing.T, sts *appsv1.StatefulSet, name, expectedValue str
|
||||
t.Errorf("%s environment variable not found", name)
|
||||
}
|
||||
|
||||
func verifyEnvVarNotPresent(t *testing.T, sts *appsv1.StatefulSet, name string) {
|
||||
t.Helper()
|
||||
for _, env := range sts.Spec.Template.Spec.Containers[0].Env {
|
||||
if env.Name == name {
|
||||
t.Errorf("environment variable %s should not be present", name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expectProxyGroupResources(t *testing.T, fc client.WithWatch, pg *tsapi.ProxyGroup, shouldExist bool, cfgHash string, proxyClass *tsapi.ProxyClass) {
|
||||
t.Helper()
|
||||
|
||||
@@ -618,3 +682,146 @@ func addNodeIDToStateSecrets(t *testing.T, fc client.WithWatch, pg *tsapi.ProxyG
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyGroupLetsEncryptStaging(t *testing.T) {
|
||||
cl := tstest.NewClock(tstest.ClockOpts{})
|
||||
zl := zap.Must(zap.NewDevelopment())
|
||||
|
||||
// Set up test cases- most are shared with non-HA Ingress.
|
||||
type proxyGroupLETestCase struct {
|
||||
leStagingTestCase
|
||||
pgType tsapi.ProxyGroupType
|
||||
}
|
||||
pcLEStaging, pcLEStagingFalse, pcOther := proxyClassesForLEStagingTest()
|
||||
sharedTestCases := testCasesForLEStagingTests(pcLEStaging, pcLEStagingFalse, pcOther)
|
||||
var tests []proxyGroupLETestCase
|
||||
for _, tt := range sharedTestCases {
|
||||
tests = append(tests, proxyGroupLETestCase{
|
||||
leStagingTestCase: tt,
|
||||
pgType: tsapi.ProxyGroupTypeIngress,
|
||||
})
|
||||
}
|
||||
tests = append(tests, proxyGroupLETestCase{
|
||||
leStagingTestCase: leStagingTestCase{
|
||||
name: "egress_pg_with_staging_proxyclass",
|
||||
proxyClassPerResource: "le-staging",
|
||||
useLEStagingEndpoint: false,
|
||||
},
|
||||
pgType: tsapi.ProxyGroupTypeEgress,
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
builder := fake.NewClientBuilder().
|
||||
WithScheme(tsapi.GlobalScheme)
|
||||
|
||||
// Pre-populate the fake client with ProxyClasses.
|
||||
builder = builder.WithObjects(pcLEStaging, pcLEStagingFalse, pcOther).
|
||||
WithStatusSubresource(pcLEStaging, pcLEStagingFalse, pcOther)
|
||||
|
||||
fc := builder.Build()
|
||||
|
||||
// If the test case needs a ProxyClass to exist, ensure it is set to Ready.
|
||||
if tt.proxyClassPerResource != "" || tt.defaultProxyClass != "" {
|
||||
name := tt.proxyClassPerResource
|
||||
if name == "" {
|
||||
name = tt.defaultProxyClass
|
||||
}
|
||||
setProxyClassReady(t, fc, cl, name)
|
||||
}
|
||||
|
||||
// Create ProxyGroup
|
||||
pg := &tsapi.ProxyGroup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
},
|
||||
Spec: tsapi.ProxyGroupSpec{
|
||||
Type: tt.pgType,
|
||||
Replicas: ptr.To[int32](1),
|
||||
ProxyClass: tt.proxyClassPerResource,
|
||||
},
|
||||
}
|
||||
mustCreate(t, fc, pg)
|
||||
|
||||
reconciler := &ProxyGroupReconciler{
|
||||
tsNamespace: tsNamespace,
|
||||
proxyImage: testProxyImage,
|
||||
defaultTags: []string{"tag:test"},
|
||||
defaultProxyClass: tt.defaultProxyClass,
|
||||
Client: fc,
|
||||
tsClient: &fakeTSClient{},
|
||||
l: zl.Sugar(),
|
||||
clock: cl,
|
||||
}
|
||||
|
||||
expectReconciled(t, reconciler, "", pg.Name)
|
||||
|
||||
// Verify that the StatefulSet created for ProxyGrup has
|
||||
// the expected setting for the staging endpoint.
|
||||
sts := &appsv1.StatefulSet{}
|
||||
if err := fc.Get(context.Background(), client.ObjectKey{Namespace: tsNamespace, Name: pg.Name}, sts); err != nil {
|
||||
t.Fatalf("failed to get StatefulSet: %v", err)
|
||||
}
|
||||
|
||||
if tt.useLEStagingEndpoint {
|
||||
verifyEnvVar(t, sts, "TS_DEBUG_ACME_DIRECTORY_URL", letsEncryptStagingEndpoint)
|
||||
} else {
|
||||
verifyEnvVarNotPresent(t, sts, "TS_DEBUG_ACME_DIRECTORY_URL")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type leStagingTestCase struct {
|
||||
name string
|
||||
// ProxyClass set on ProxyGroup or Ingress resource.
|
||||
proxyClassPerResource string
|
||||
// Default ProxyClass.
|
||||
defaultProxyClass string
|
||||
useLEStagingEndpoint bool
|
||||
}
|
||||
|
||||
// Shared test cases for LE staging endpoint configuration for ProxyGroup and
|
||||
// non-HA Ingress.
|
||||
func testCasesForLEStagingTests(pcLEStaging, pcLEStagingFalse, pcOther *tsapi.ProxyClass) []leStagingTestCase {
|
||||
return []leStagingTestCase{
|
||||
{
|
||||
name: "with_staging_proxyclass",
|
||||
proxyClassPerResource: "le-staging",
|
||||
useLEStagingEndpoint: true,
|
||||
},
|
||||
{
|
||||
name: "with_staging_proxyclass_false",
|
||||
proxyClassPerResource: "le-staging-false",
|
||||
useLEStagingEndpoint: false,
|
||||
},
|
||||
{
|
||||
name: "with_other_proxyclass",
|
||||
proxyClassPerResource: "other",
|
||||
useLEStagingEndpoint: false,
|
||||
},
|
||||
{
|
||||
name: "no_proxyclass",
|
||||
proxyClassPerResource: "",
|
||||
useLEStagingEndpoint: false,
|
||||
},
|
||||
{
|
||||
name: "with_default_staging_proxyclass",
|
||||
proxyClassPerResource: "",
|
||||
defaultProxyClass: "le-staging",
|
||||
useLEStagingEndpoint: true,
|
||||
},
|
||||
{
|
||||
name: "with_default_other_proxyclass",
|
||||
proxyClassPerResource: "",
|
||||
defaultProxyClass: "other",
|
||||
useLEStagingEndpoint: false,
|
||||
},
|
||||
{
|
||||
name: "with_default_staging_proxyclass_false",
|
||||
proxyClassPerResource: "",
|
||||
defaultProxyClass: "le-staging-false",
|
||||
useLEStagingEndpoint: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user