Skip to content

Commit 4a6223b

Browse files
feat(adminconsole): Mount Host CA Bundle into Admin Console Containers (#2175)
* feat(velero): add support for mitm proxy * f * f * f * f * f * f * f * velero use ca from host * http proxy dryrun test * f * f * f * f * f * f * f * f * f * f * f * f * f * f * f * f * f * f * f * refactor host ca retrieval * add unit tests * add dry run capability for adminconsole addon * make adminconsole unit test consistent with other addons * remove privateCa from admin console * update adminconsole version and add integration test * add tests for cabundle to dryrun * update TestInstallWithPrivateCAs e2e test * fix dry run tests to make sure they use dry run client * more debug * more debug * fix tests * fix tests * remove debug values * debug integration test * fix integration test * move cert generation to bash * debug privateca e2e test * debug privateca e2e test * re-add privateca in order to get operator working correctly for mitm tests * fix create configmap * remove uneeded e2e test * consolidate unit test * remove privateca from static values * remove redundant dry run test --------- Co-authored-by: Ethan Mosbaugh <ethan@replicated.com>
1 parent 8d0a1e3 commit 4a6223b

File tree

11 files changed

+379
-129
lines changed

11 files changed

+379
-129
lines changed

e2e/install_test.go

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package e2e
22

33
import (
44
"encoding/base64"
5-
"encoding/json"
65
"fmt"
76
"os"
87
"strings"
@@ -11,12 +10,10 @@ import (
1110

1211
"github.com/google/uuid"
1312
"github.com/stretchr/testify/require"
14-
corev1 "k8s.io/api/core/v1"
1513

1614
"github.com/replicatedhq/embedded-cluster/e2e/cluster/cmx"
1715
"github.com/replicatedhq/embedded-cluster/e2e/cluster/docker"
1816
"github.com/replicatedhq/embedded-cluster/e2e/cluster/lxd"
19-
"github.com/replicatedhq/embedded-cluster/pkg/certs"
2017
)
2118

2219
func TestSingleNodeInstallation(t *testing.T) {
@@ -1728,62 +1725,6 @@ func TestFiveNodesAirgapUpgrade(t *testing.T) {
17281725
t.Logf("%s: test complete", time.Now().Format(time.RFC3339))
17291726
}
17301727

1731-
func TestInstallWithPrivateCAs(t *testing.T) {
1732-
RequireEnvVars(t, []string{"SHORT_SHA"})
1733-
1734-
input := &lxd.ClusterInput{
1735-
T: t,
1736-
Nodes: 1,
1737-
Image: "ubuntu/jammy",
1738-
LicensePath: "licenses/license.yaml",
1739-
EmbeddedClusterPath: "../output/bin/embedded-cluster",
1740-
}
1741-
tc := lxd.NewCluster(input)
1742-
defer tc.Cleanup()
1743-
1744-
certBuilder, err := certs.NewBuilder()
1745-
require.NoError(t, err, "unable to create new cert builder")
1746-
crtContent, _, err := certBuilder.Generate()
1747-
require.NoError(t, err, "unable to build test certificate")
1748-
1749-
tmpfile, err := os.CreateTemp("", "test-temp-cert-*.crt")
1750-
require.NoError(t, err, "unable to create temp file")
1751-
defer os.Remove(tmpfile.Name())
1752-
1753-
_, err = tmpfile.WriteString(crtContent)
1754-
require.NoError(t, err, "unable to write to temp file")
1755-
tmpfile.Close()
1756-
1757-
lxd.CopyFileToNode(input, tc.Nodes[0], lxd.File{
1758-
SourcePath: tmpfile.Name(),
1759-
DestPath: "/tmp/ca.crt",
1760-
Mode: 0666,
1761-
})
1762-
1763-
installSingleNodeWithOptions(t, tc, installOptions{
1764-
privateCA: "/tmp/ca.crt",
1765-
})
1766-
1767-
if _, _, err := tc.SetupPlaywrightAndRunTest("deploy-app"); err != nil {
1768-
t.Fatalf("fail to run playwright test deploy-app: %v", err)
1769-
}
1770-
1771-
checkInstallationState(t, tc)
1772-
1773-
t.Logf("checking if the configmap was created with the right values")
1774-
line := []string{"kubectl", "get", "cm", "kotsadm-private-cas", "-n", "kotsadm", "-o", "json"}
1775-
stdout, _, err := tc.RunCommandOnNode(0, line, lxd.WithECShellEnv("/var/lib/embedded-cluster"))
1776-
require.NoError(t, err, "unable get kotsadm-private-cas configmap")
1777-
1778-
var cm corev1.ConfigMap
1779-
err = json.Unmarshal([]byte(stdout), &cm)
1780-
require.NoErrorf(t, err, "unable to unmarshal output to configmap: %q", stdout)
1781-
require.Contains(t, cm.Data, "ca_0.crt", "index ca_0.crt not found in ca secret")
1782-
require.Equal(t, crtContent, cm.Data["ca_0.crt"], "content mismatch")
1783-
1784-
t.Logf("%s: test complete", time.Now().Format(time.RFC3339))
1785-
}
1786-
17871728
func TestInstallWithConfigValues(t *testing.T) {
17881729
t.Parallel()
17891730

pkg/addons/adminconsole/adminconsole.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ type AdminConsole struct {
2626
ReplicatedAppDomain string
2727
ProxyRegistryDomain string
2828
ReplicatedRegistryDomain string
29+
HostCABundlePath string
30+
31+
// DryRun is a flag to enable dry-run mode for Admin Console.
32+
// If true, Admin Console will only render the helm template and additional manifests, but not install
33+
// the release.
34+
DryRun bool
35+
36+
dryRunManifests [][]byte
2937
}
3038

3139
type KotsInstaller func(msg *spinner.MessageWriter) error
@@ -110,3 +118,8 @@ func (a *AdminConsole) ChartLocation() string {
110118
}
111119
return chartName
112120
}
121+
122+
// DryRunManifests returns the manifests generated during a dry run.
123+
func (a *AdminConsole) DryRunManifests() [][]byte {
124+
return a.dryRunManifests
125+
}

pkg/addons/adminconsole/install.go

Lines changed: 113 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package adminconsole
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/base64"
67
"fmt"
@@ -9,13 +10,27 @@ import (
910
"github.com/pkg/errors"
1011
"github.com/replicatedhq/embedded-cluster/pkg/addons/registry"
1112
"github.com/replicatedhq/embedded-cluster/pkg/helm"
13+
"github.com/replicatedhq/embedded-cluster/pkg/kubeutils"
1214
"github.com/replicatedhq/embedded-cluster/pkg/spinner"
1315
"golang.org/x/crypto/bcrypt"
1416
corev1 "k8s.io/api/core/v1"
1517
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
"k8s.io/apimachinery/pkg/runtime"
19+
jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
1620
"sigs.k8s.io/controller-runtime/pkg/client"
1721
)
1822

23+
var (
24+
serializer runtime.Serializer
25+
)
26+
27+
func init() {
28+
scheme := kubeutils.Scheme
29+
serializer = jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, scheme, scheme, jsonserializer.SerializerOptions{
30+
Yaml: true,
31+
})
32+
}
33+
1934
func (a *AdminConsole) Install(ctx context.Context, kcli client.Client, hcli helm.Client, overrides []string, writer *spinner.MessageWriter) error {
2035
// some resources are not part of the helm chart and need to be created before the chart is installed
2136
// TODO: move this to the helm chart
@@ -28,40 +43,49 @@ func (a *AdminConsole) Install(ctx context.Context, kcli client.Client, hcli hel
2843
return errors.Wrap(err, "generate helm values")
2944
}
3045

31-
_, err = hcli.Install(ctx, helm.InstallOptions{
46+
opts := helm.InstallOptions{
3247
ReleaseName: releaseName,
3348
ChartPath: a.ChartLocation(),
3449
ChartVersion: Metadata.Version,
3550
Values: values,
3651
Namespace: namespace,
3752
Labels: getBackupLabels(),
38-
})
39-
if err != nil {
40-
return errors.Wrap(err, "helm install")
4153
}
4254

43-
// install the application
44-
45-
if a.KotsInstaller != nil {
46-
err := a.KotsInstaller(writer)
55+
if a.DryRun {
56+
manifests, err := hcli.Render(ctx, opts)
4757
if err != nil {
48-
return err
58+
return errors.Wrap(err, "dry run render")
59+
}
60+
a.dryRunManifests = append(a.dryRunManifests, manifests...)
61+
} else {
62+
_, err = hcli.Install(ctx, opts)
63+
if err != nil {
64+
return errors.Wrap(err, "helm install")
65+
}
66+
67+
// install the application
68+
if a.KotsInstaller != nil {
69+
err := a.KotsInstaller(writer)
70+
if err != nil {
71+
return err
72+
}
4973
}
5074
}
5175

5276
return nil
5377
}
5478

5579
func (a *AdminConsole) createPreRequisites(ctx context.Context, kcli client.Client) error {
56-
if err := createNamespace(ctx, kcli, namespace); err != nil {
80+
if err := a.createNamespace(ctx, kcli, namespace); err != nil {
5781
return errors.Wrap(err, "create namespace")
5882
}
5983

60-
if err := createPasswordSecret(ctx, kcli, namespace, a.Password); err != nil {
84+
if err := a.createPasswordSecret(ctx, kcli, namespace, a.Password); err != nil {
6185
return errors.Wrap(err, "create kots password secret")
6286
}
6387

64-
if err := createCAConfigmap(ctx, kcli, namespace, a.PrivateCAs); err != nil {
88+
if err := a.createCAConfigmap(ctx, kcli, namespace, a.PrivateCAs); err != nil {
6589
return errors.Wrap(err, "create kots CA configmap")
6690
}
6791

@@ -70,90 +94,115 @@ func (a *AdminConsole) createPreRequisites(ctx context.Context, kcli client.Clie
7094
if err != nil {
7195
return errors.Wrap(err, "get registry cluster IP")
7296
}
73-
if err := createRegistrySecret(ctx, kcli, namespace, registryIP); err != nil {
97+
if err := a.createRegistrySecret(ctx, kcli, namespace, registryIP); err != nil {
7498
return errors.Wrap(err, "create registry secret")
7599
}
76100
}
77101

78102
return nil
79103
}
80104

81-
func createNamespace(ctx context.Context, kcli client.Client, namespace string) error {
82-
ns := corev1.Namespace{
83-
ObjectMeta: metav1.ObjectMeta{
84-
Name: namespace,
85-
},
86-
}
87-
if err := kcli.Create(ctx, &ns); client.IgnoreAlreadyExists(err) != nil {
88-
return err
89-
}
90-
return nil
91-
}
92-
93-
func createPasswordSecret(ctx context.Context, kcli client.Client, namespace string, password string) error {
94-
passwordBcrypt, err := bcrypt.GenerateFromPassword([]byte(password), 10)
105+
func (a *AdminConsole) createCAConfigmap(ctx context.Context, cli client.Client, namespace string, privateCAs []string) error {
106+
cas, err := privateCAsToMap(privateCAs)
95107
if err != nil {
96-
return errors.Wrap(err, "generate bcrypt from password")
108+
return errors.Wrap(err, "create private cas map")
97109
}
98110

99-
kotsPasswordSecret := corev1.Secret{
111+
kotsCAConfigmap := corev1.ConfigMap{
100112
TypeMeta: metav1.TypeMeta{
101-
Kind: "Secret",
113+
Kind: "ConfigMap",
102114
APIVersion: "v1",
103115
},
104116
ObjectMeta: metav1.ObjectMeta{
105-
Name: "kotsadm-password",
117+
Name: "kotsadm-private-cas",
106118
Namespace: namespace,
107119
Labels: map[string]string{
108120
"kots.io/kotsadm": "true",
109121
"replicated.com/disaster-recovery": "infra",
110122
"replicated.com/disaster-recovery-chart": "admin-console",
111123
},
112124
},
113-
Data: map[string][]byte{
114-
"passwordBcrypt": []byte(passwordBcrypt),
115-
},
125+
Data: cas,
116126
}
117127

118-
err = kcli.Create(ctx, &kotsPasswordSecret)
119-
if err != nil {
120-
return errors.Wrap(err, "create kotsadm-password secret")
128+
if a.DryRun {
129+
b := bytes.NewBuffer(nil)
130+
if err := serializer.Encode(&kotsCAConfigmap, b); err != nil {
131+
return errors.Wrap(err, "serialize CA configmap")
132+
}
133+
a.dryRunManifests = append(a.dryRunManifests, b.Bytes())
134+
} else {
135+
if err := cli.Create(ctx, &kotsCAConfigmap); client.IgnoreAlreadyExists(err) != nil {
136+
return errors.Wrap(err, "create kotsadm-private-cas configmap")
137+
}
121138
}
122139

123140
return nil
124141
}
125142

126-
func createCAConfigmap(ctx context.Context, cli client.Client, namespace string, privateCAs []string) error {
127-
cas, err := privateCAsToMap(privateCAs)
143+
func (a *AdminConsole) createNamespace(ctx context.Context, kcli client.Client, namespace string) error {
144+
ns := corev1.Namespace{
145+
ObjectMeta: metav1.ObjectMeta{
146+
Name: namespace,
147+
},
148+
}
149+
150+
if a.DryRun {
151+
b := bytes.NewBuffer(nil)
152+
if err := serializer.Encode(&ns, b); err != nil {
153+
return errors.Wrap(err, "serialize namespace")
154+
}
155+
a.dryRunManifests = append(a.dryRunManifests, b.Bytes())
156+
} else {
157+
if err := kcli.Create(ctx, &ns); client.IgnoreAlreadyExists(err) != nil {
158+
return err
159+
}
160+
}
161+
return nil
162+
}
163+
164+
func (a *AdminConsole) createPasswordSecret(ctx context.Context, kcli client.Client, namespace string, password string) error {
165+
passwordBcrypt, err := bcrypt.GenerateFromPassword([]byte(password), 10)
128166
if err != nil {
129-
return errors.Wrap(err, "create private cas map")
167+
return errors.Wrap(err, "generate bcrypt from password")
130168
}
131169

132-
kotsCAConfigmap := corev1.ConfigMap{
170+
kotsPasswordSecret := corev1.Secret{
133171
TypeMeta: metav1.TypeMeta{
134-
Kind: "ConfigMap",
172+
Kind: "Secret",
135173
APIVersion: "v1",
136174
},
137175
ObjectMeta: metav1.ObjectMeta{
138-
Name: "kotsadm-private-cas",
176+
Name: "kotsadm-password",
139177
Namespace: namespace,
140178
Labels: map[string]string{
141179
"kots.io/kotsadm": "true",
142180
"replicated.com/disaster-recovery": "infra",
143181
"replicated.com/disaster-recovery-chart": "admin-console",
144182
},
145183
},
146-
Data: cas,
184+
Data: map[string][]byte{
185+
"passwordBcrypt": []byte(passwordBcrypt),
186+
},
147187
}
148188

149-
if err := cli.Create(ctx, &kotsCAConfigmap); client.IgnoreAlreadyExists(err) != nil {
150-
return errors.Wrap(err, "create kotsadm-private-cas configmap")
189+
if a.DryRun {
190+
b := bytes.NewBuffer(nil)
191+
if err := serializer.Encode(&kotsPasswordSecret, b); err != nil {
192+
return errors.Wrap(err, "serialize password secret")
193+
}
194+
a.dryRunManifests = append(a.dryRunManifests, b.Bytes())
195+
} else {
196+
err = kcli.Create(ctx, &kotsPasswordSecret)
197+
if err != nil {
198+
return errors.Wrap(err, "create kotsadm-password secret")
199+
}
151200
}
152201

153202
return nil
154203
}
155204

156-
func createRegistrySecret(ctx context.Context, kcli client.Client, namespace string, registryIP string) error {
205+
func (a *AdminConsole) createRegistrySecret(ctx context.Context, kcli client.Client, namespace string, registryIP string) error {
157206
authString := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("embedded-cluster:%s", registry.GetRegistryPassword())))
158207
authConfig := fmt.Sprintf(`{"auths":{"%s:5000":{"username": "embedded-cluster", "password": "%s", "auth": "%s"}}}`, registryIP, registry.GetRegistryPassword(), authString)
159208

@@ -177,16 +226,30 @@ func createRegistrySecret(ctx context.Context, kcli client.Client, namespace str
177226
Type: "kubernetes.io/dockerconfigjson",
178227
}
179228

180-
err := kcli.Create(ctx, &registryCreds)
181-
if err != nil {
182-
return errors.Wrap(err, "create registry-auth secret")
229+
if a.DryRun {
230+
b := bytes.NewBuffer(nil)
231+
if err := serializer.Encode(&registryCreds, b); err != nil {
232+
return errors.Wrap(err, "serialize registry secret")
233+
}
234+
a.dryRunManifests = append(a.dryRunManifests, b.Bytes())
235+
} else {
236+
err := kcli.Create(ctx, &registryCreds)
237+
if err != nil {
238+
return errors.Wrap(err, "create registry-auth secret")
239+
}
183240
}
184241

185242
return nil
186243
}
187244

188245
func privateCAsToMap(privateCAs []string) (map[string]string, error) {
189246
cas := map[string]string{}
247+
248+
// Handle nil privateCAs
249+
if privateCAs == nil {
250+
return cas, nil
251+
}
252+
190253
for i, path := range privateCAs {
191254
data, err := os.ReadFile(path)
192255
if err != nil {

0 commit comments

Comments
 (0)