Skip to content

Commit 04b4d0a

Browse files
authored
feat: require that the provided license matches the binary (#397)
* update ChannelRelease object to parse slug and channel * check license upon initial install * update test app release file * do not provide license to host preflight tests * do not provide license to unsupported override test * add license+norelease and release+nolicense tests
1 parent 1f607f4 commit 04b4d0a

File tree

10 files changed

+66
-5
lines changed

10 files changed

+66
-5
lines changed

.github/workflows/pull-request.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ jobs:
7878
if: github.actor != 'dependabot[bot]'
7979
run: |
8080
export SHORT_SHA=$(git rev-parse --short=7 HEAD)
81-
echo "# channel release object" > e2e/kots-release-install/release.yaml
8281
echo "versionLabel: \"${SHORT_SHA}\"" >> e2e/kots-release-install/release.yaml
8382
cat e2e/kots-release-install/release.yaml
8483
cp output/bin/embedded-cluster output/bin/embedded-cluster-original

.github/workflows/release-dev.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ jobs:
5959
if: github.actor != 'dependabot[bot]'
6060
run: |
6161
export SHORT_SHA=$(git rev-parse --short=7 HEAD)
62-
echo "# channel release object" > e2e/kots-release-install/release.yaml
6362
echo "versionLabel: \"${SHORT_SHA}\"" >> e2e/kots-release-install/release.yaml
6463
cat e2e/kots-release-install/release.yaml
6564
cp output/bin/embedded-cluster output/bin/embedded-cluster-original

cmd/embedded-cluster/install.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,47 @@ func isAlreadyInstalled() (bool, error) {
118118
}
119119
}
120120

121+
func checkLicenseMatches(c *cli.Context) error {
122+
rel, err := release.GetChannelRelease()
123+
if err != nil {
124+
return fmt.Errorf("failed to get release from binary: %w", err) // this should only be if the release is malformed
125+
}
126+
127+
// handle the three cases that do not require parsing the license file
128+
// 1. no release and no license, which is OK
129+
// 2. no license and a release, which is not OK
130+
// 3. a license and no release, which is not OK
131+
if rel == nil && c.String("license") == "" {
132+
// no license and no release, this is OK
133+
return nil
134+
} else if rel == nil && c.String("license") != "" {
135+
// license is present but no release, this means we would install without vendor charts and k0s overrides
136+
return fmt.Errorf("a license was provided but no release was found in binary")
137+
} else if rel != nil && c.String("license") == "" {
138+
// release is present but no license, this is not OK
139+
return fmt.Errorf("no license was provided for %s", rel.AppSlug)
140+
}
141+
142+
license, err := helpers.ParseLicense(c.String("license"))
143+
if err != nil {
144+
return fmt.Errorf("unable to parse license: %w", err)
145+
}
146+
147+
// Check if the license matches the application version data
148+
if rel.AppSlug != license.Spec.AppSlug {
149+
// if the app is different, we will not be able to provide the correct vendor supplied charts and k0s overrides
150+
return fmt.Errorf("license app %s does not match binary app %s", license.Spec.AppSlug, rel.AppSlug)
151+
}
152+
if rel.ChannelID != license.Spec.ChannelID {
153+
// if the channel is different, we will not be able to install the pinned vendor application version within kots
154+
// this may result in an immediate k8s upgrade after installation, which is undesired
155+
return fmt.Errorf("license channel %s (%s) does not match binary channel %s", license.Spec.ChannelID, license.Spec.ChannelName, rel.ChannelID)
156+
}
157+
158+
return nil
159+
160+
}
161+
121162
// createK0sConfig creates a new k0s.yaml configuration file. The file is saved in the
122163
// global location (as returned by defaults.PathToK0sConfig()). If a file already sits
123164
// there, this function returns an error.
@@ -296,6 +337,12 @@ var installCommand = &cli.Command{
296337
return ErrNothingElseToAdd
297338
}
298339
metrics.ReportApplyStarted(c)
340+
logrus.Debugf("checking license matches")
341+
if err := checkLicenseMatches(c); err != nil {
342+
err := fmt.Errorf("unable to check license: %w", err)
343+
metrics.ReportApplyFinished(c, err)
344+
return err
345+
}
299346
logrus.Debugf("materializing binaries")
300347
if err := goods.Materialize(); err != nil {
301348
err := fmt.Errorf("unable to materialize binaries: %w", err)

e2e/install_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ func TestInstallWithoutEmbed(t *testing.T) {
342342
T: t,
343343
Nodes: 1,
344344
Image: "rockylinux/8",
345+
LicensePath: "license.yaml",
345346
EmbeddedClusterPath: "../output/bin/embedded-cluster-original",
346347
})
347348
defer tc.Destroy()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# channel release object
2+
channelID: "2cHXb1RCttzpR0xvnNWyaZCgDBP"
3+
appSlug: "embedded-cluster-smoke-test-staging-app"

e2e/scripts/default-install.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ check_openebs_storage_class() {
5656
}
5757

5858
main() {
59+
if embedded-cluster install --no-prompt --license /tmp/license.yaml 2>&1 | tee /tmp/log ; then
60+
echo "Expected installation to fail with a license provided"
61+
exit 1
62+
fi
63+
5964
if ! embedded-cluster install --no-prompt 2>&1 | tee /tmp/log ; then
6065
cat /etc/os-release
6166
echo "Failed to install embedded-cluster"

e2e/scripts/embedded-preflight.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ wait_for_healthy_node() {
151151
main() {
152152
cp -Rfp /usr/local/bin/embedded-cluster /usr/local/bin/embedded-cluster-copy
153153
embed_preflight "$preflight_with_failure"
154-
if embedded-cluster install --no-prompt --license /tmp/license.yaml 2>&1 | tee /tmp/log ; then
154+
if embedded-cluster install --no-prompt 2>&1 | tee /tmp/log ; then
155155
cat /tmp/log
156156
echo "Expected installation to fail"
157157
exit 1
@@ -163,7 +163,7 @@ main() {
163163
fi
164164
mv /tmp/log /tmp/log-failure
165165
embed_preflight "$preflight_with_warning"
166-
if ! embedded-cluster install --no-prompt --license /tmp/license.yaml 2>&1 | tee /tmp/log ; then
166+
if ! embedded-cluster install --no-prompt 2>&1 | tee /tmp/log ; then
167167
cat /etc/os-release
168168
echo "Failed to install embedded-cluster"
169169
exit 1

e2e/scripts/single-node-install.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ ensure_app_not_upgraded() {
120120
}
121121

122122
main() {
123+
if embedded-cluster install --no-prompt 2>&1 | tee /tmp/log ; then
124+
echo "Expected installation to fail without a license provided"
125+
exit 1
126+
fi
127+
123128
if ! embedded-cluster install --no-prompt --license /tmp/license.yaml 2>&1 | tee /tmp/log ; then
124129
cat /etc/os-release
125130
echo "Failed to install embedded-cluster"

e2e/scripts/unsupported-overrides.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ wait_for_memcached_pods() {
127127
main() {
128128
cp -Rfp /usr/local/bin/embedded-cluster /usr/local/bin/embedded-cluster-copy
129129
embed_cluster_config "$embedded_cluster_config"
130-
if ! embedded-cluster install --no-prompt --license /tmp/license.yaml 2>&1 | tee /tmp/log ; then
130+
if ! embedded-cluster install --no-prompt 2>&1 | tee /tmp/log ; then
131131
echo "Failed to install embedded-cluster"
132132
cat /tmp/log
133133
exit 1

pkg/release/release.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ func (r *ReleaseData) GetEmbeddedClusterConfig() (*embeddedclusterv1beta1.Config
137137
// ChannelRelease contains information about a specific app release inside a channel.
138138
type ChannelRelease struct {
139139
VersionLabel string `yaml:"versionLabel"`
140+
ChannelID string `yaml:"channelID"`
141+
AppSlug string `yaml:"appSlug"`
140142
}
141143

142144
// GetChannelRelease reads the embedded channel release object. If no channel release

0 commit comments

Comments
 (0)