Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions controllers/istio_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ package controllers
import (
"context"
"fmt"
istiocrmetrics "github.com/kyma-project/istio/operator/internal/metrics"
"time"

istiocrmetrics "github.com/kyma-project/istio/operator/internal/metrics"

"github.com/pkg/errors"

"github.com/kyma-project/istio/operator/internal/images"
Expand Down Expand Up @@ -122,9 +123,9 @@ func (r *IstioReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return r.terminateReconciliation(ctx, &istioCR, describederrors.NewDescribedError(imgErr, "Unable to get Istio images environments"),
operatorv1alpha2.NewReasonWithMessage(operatorv1alpha2.ConditionReasonReconcileFailed))
}
hub, imgErr := istioImages.GetHub()
registryAndTag, imgErr := istioImages.GetImageRegistryAndTag()
if imgErr != nil {
return r.terminateReconciliation(ctx, &istioCR, describederrors.NewDescribedError(imgErr, "Unable to get Istio images hub"),
return r.terminateReconciliation(ctx, &istioCR, describederrors.NewDescribedError(imgErr, "Unable to get Istio images registryAndTag"),
operatorv1alpha2.NewReasonWithMessage(operatorv1alpha2.ConditionReasonReconcileFailed))
}

Expand Down Expand Up @@ -179,7 +180,7 @@ func (r *IstioReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
}
}

istioImageVersion, installationErr := r.istioInstallation.Reconcile(ctx, &istioCR, r.statusHandler, hub)
istioImageVersion, installationErr := r.istioInstallation.Reconcile(ctx, &istioCR, r.statusHandler, registryAndTag)
if installationErr != nil {
return r.requeueReconciliation(ctx, &istioCR, installationErr,
operatorv1alpha2.NewReasonWithMessage(operatorv1alpha2.ConditionReasonIstioInstallUninstallFailed),
Expand Down
3 changes: 2 additions & 1 deletion controllers/istio_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"time"

"github.com/kyma-project/istio/operator/internal/images"
"istio.io/api/networking/v1alpha3"

"k8s.io/utils/ptr"
Expand Down Expand Up @@ -1198,7 +1199,7 @@ type istioInstallationReconciliationMock struct {
err describederrors.DescribedError
}

func (i *istioInstallationReconciliationMock) Reconcile(_ context.Context, _ *operatorv1alpha2.Istio, _ status.Status, _ string) (istiooperator.IstioImageVersion, describederrors.DescribedError) {
func (i *istioInstallationReconciliationMock) Reconcile(_ context.Context, _ *operatorv1alpha2.Istio, _ status.Status, _ images.RegistryAndTag) (istiooperator.IstioImageVersion, describederrors.DescribedError) {
version, err := istiooperator.NewIstioImageVersionFromTag("1.16.0-distroless")
if err != nil {
i.err = describederrors.NewDescribedError(err, "error creating IstioImageVersion")
Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes/1.24.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@
See [#1710](https://github.com/kyma-project/istio/pull/1710).
- We've added support for **forwardClientCertDetails**.
See [#1715](https://github.com/kyma-project/istio/pull/1715) and [Istio Custom Resource](https://kyma-project.io/external-content/istio/docs/user/04-00-istio-custom-resource.html).
- Add support for the **KYMA_FIPS_MODE_ENABLED** environment variable, which allows configuring separate Istio fips images.
See [#1721](https://github.com/kyma-project/istio/pull/1721).
66 changes: 58 additions & 8 deletions internal/images/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ package images

import (
"fmt"
"os"
"strings"

"github.com/caarlos0/env/v11"
)

const kymaFipsModeEnabledEnv = "KYMA_FIPS_MODE_ENABLED"

type Image string

type RegistryAndTag struct {
Hub string
Tag string
}

func (i Image) GetHub() (string, error) {
if i == "" {
return "", fmt.Errorf("image can not be empty")
Expand All @@ -22,14 +30,43 @@ func (i Image) GetHub() (string, error) {
return strings.Join(parts[:len(parts)-1], "/"), nil
}

func (i Image) GetTag() (string, error) {
if i == "" {
return "", fmt.Errorf("image can not be empty")
}

parts := strings.Split(string(i), ":")
if len(parts) != 2 {
return "", fmt.Errorf("image %s does not contain a valid tag", i)
}

return strings.Join(parts[len(parts)-1:], "/"), nil
}

type Images struct {
Pilot Image `env:"pilot,notEmpty"`
InstallCNI Image `env:"install-cni,notEmpty"`
ProxyV2 Image `env:"proxyv2,notEmpty"`
Ztunnel Image `env:"ztunnel,notEmpty"`
}

type ImagesFips struct {
Pilot Image `env:"pilot-fips,notEmpty"`
InstallCNI Image `env:"install-cni-fips,notEmpty"`
ProxyV2 Image `env:"proxyv2-fips,notEmpty"`
Ztunnel Image `env:"ztunnel-fips,notEmpty"`
}

func GetImages() (*Images, error) {
kymaFipsModeEnabled := os.Getenv(kymaFipsModeEnabledEnv)
if kymaFipsModeEnabled == "true" {
environments, err := env.ParseAs[ImagesFips]()
if err != nil {
return nil, fmt.Errorf("missing required environment variables %w", err)
}
return (*Images)(&environments), nil
}

environments, err := env.ParseAs[Images]()
if err != nil {
return nil, fmt.Errorf("missing required environment variables %w", err)
Expand All @@ -38,23 +75,36 @@ func GetImages() (*Images, error) {
return &environments, nil
}

func (e *Images) GetHub() (string, error) {
environments := []Image{e.Pilot, e.InstallCNI, e.ProxyV2}
func (e *Images) GetImageRegistryAndTag() (RegistryAndTag, error) {
environments := []Image{e.Pilot, e.InstallCNI, e.ProxyV2, e.Ztunnel}

initialHub, err := environments[0].GetHub()
if err != nil {
return "", fmt.Errorf("failed to get hub for image %s: %w", environments[0], err)
return RegistryAndTag{}, fmt.Errorf("failed to get hub for image %s: %w", environments[0], err)
}
// Ensure that all required images are from the same hub
initialTag, err := environments[0].GetTag()
if err != nil {
return RegistryAndTag{}, fmt.Errorf("failed to get tag for image %s: %w", environments[0], err)
}

// Ensure that all required images are from the same hub and have the same version tag
for _, image := range environments {
currentHub, err := image.GetHub()
if err != nil {
return "", fmt.Errorf("failed to get hub for image %s: %w", image, err)
return RegistryAndTag{}, fmt.Errorf("failed to get hub for image %s: %w", image, err)
}

if currentHub != initialHub {
return "", fmt.Errorf("image %s is not from the same hub as %s", image, initialHub)
return RegistryAndTag{}, fmt.Errorf("image %s is not from the same hub as %s", image, environments[0])
}

currentTag, err := image.GetTag()
if err != nil {
return RegistryAndTag{}, fmt.Errorf("failed to get tag for image %s: %w", image, err)
}
if currentTag != initialTag {
return RegistryAndTag{}, fmt.Errorf("image %s does not have the same tag as %s", image, environments[0])
}
}
return initialHub, nil

return RegistryAndTag{Hub: initialHub, Tag: initialTag}, nil
}
107 changes: 99 additions & 8 deletions internal/images/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package images_test

import (
"fmt"
"os"
"testing"

. "github.com/onsi/ginkgo/v2"
Expand All @@ -16,26 +17,27 @@ func TestEnvs(t *testing.T) {
RunSpecs(t, "Environment Suite")
}

var _ = Describe("Images.GetHub", func() {
var _ = Describe("Images.GetImageRegistryAndTag", func() {
type fields struct {
Pilot images.Image
InstallCNI images.Image
ProxyV2 images.Image
Ztunnel images.Image
}

DescribeTable("GetHub",
func(f fields, want string, wantErr bool, err error) {
DescribeTable("GetImageRegistryAndTag",
func(f fields, want images.RegistryAndTag, wantErr bool, expErr error) {
e := &images.Images{
Pilot: f.Pilot,
InstallCNI: f.InstallCNI,
ProxyV2: f.ProxyV2,
Ztunnel: f.Ztunnel,
}
got, err := e.GetHub()
got, err := e.GetImageRegistryAndTag()
if wantErr {
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("image"))
Expect(err.Error()).To(ContainSubstring(expErr.Error()))
} else {
Expect(err).NotTo(HaveOccurred())
Expect(got).To(Equal(want))
Expand All @@ -48,31 +50,120 @@ var _ = Describe("Images.GetHub", func() {
ProxyV2: "docker.io/istio/proxyv2:1.10.0",
Ztunnel: "docker.io/istio/ztunnel:1.10.0",
},
"docker.io/istio",
images.RegistryAndTag{Hub: "docker.io/istio", Tag: "1.10.0"},
false,
nil,
),
Entry("invalid image format",
Entry("invalid image hub",
fields{
Pilot: "pilot:1.10.0",
InstallCNI: "docker.io/istio/cni:1.10.0",
ProxyV2: "docker.io/istio/proxyv2:1.10.0",
Ztunnel: "docker.io/istio/ztunnel:1.10.0",
},
"",
images.RegistryAndTag{},
true,
fmt.Errorf("image pilot:1.10.0 does not contain a valid hub URL"),
),
Entry("missing image tag",
fields{
Pilot: "docker.io/istio/pilot1.10.0",
InstallCNI: "docker.io/istio/cni:1.10.0",
ProxyV2: "docker.io/istio/proxyv2:1.10.0",
Ztunnel: "docker.io/istio/ztunnel:1.10.0",
},
images.RegistryAndTag{},
true,
fmt.Errorf("image docker.io/istio/pilot1.10.0 does not contain a valid tag"),
),
Entry("images from different hubs",
fields{
Pilot: "docker.io/istio/pilot:1.10.0",
InstallCNI: "docker.io/istio/cni:1.10.0",
ProxyV2: "foo.bar/istio/proxyv2:1.10.0",
Ztunnel: "docker.io/istio/ztunnel:1.10.0",
},
"",
images.RegistryAndTag{},
true,
fmt.Errorf("image foo.bar/istio/proxyv2:1.10.0 is not from the same hub as docker.io/istio/pilot:1.10.0"),
),
Entry("images with different tags",
fields{
Pilot: "docker.io/istio/pilot:1.10.0",
InstallCNI: "docker.io/istio/cni:1.10.0",
ProxyV2: "docker.io/istio/proxyv2:1.11.0",
Ztunnel: "docker.io/istio/ztunnel:1.10.0",
},
images.RegistryAndTag{},
true,
fmt.Errorf("image docker.io/istio/proxyv2:1.11.0 does not have the same tag as docker.io/istio/pilot:1.10.0"),
),
Entry("empty image",
fields{
Pilot: "",
InstallCNI: "docker.io/istio/cni:1.10.0",
ProxyV2: "docker.io/istio/proxyv2:1.10.0",
},
images.RegistryAndTag{},
true,
fmt.Errorf("image can not be empty"),
),
)
})

var _ = Describe("Images.GetFipsImages", func() {
_ = os.Setenv("pilot", "docker.io/istio/pilot:1.10.0")
_ = os.Setenv("install-cni", "docker.io/istio/cni:1.10.0")
_ = os.Setenv("proxyv2", "docker.io/istio/proxyv2:1.10.0")
_ = os.Setenv("ztunnel", "docker.io/istio/ztunnel:1.10.0")

Context("when KYMA_FIPS_MODE_ENABLED is true", func() {
It("should set the FIPS images", func() {
_ = os.Setenv("KYMA_FIPS_MODE_ENABLED", "true")
_ = os.Setenv("pilot-fips", "docker.io/istio/pilot-fips:1.10.0")
_ = os.Setenv("install-cni-fips", "docker.io/istio/cni-fips:1.10.0")
_ = os.Setenv("proxyv2-fips", "docker.io/istio/proxyv2-fips:1.10.0")
_ = os.Setenv("ztunnel-fips", "docker.io/istio/ztunnel-fips:1.10.0")

e, err := images.GetImages()
Expect(err).NotTo(HaveOccurred())
Expect(e.Pilot).To(Equal(images.Image("docker.io/istio/pilot-fips:1.10.0")))
Expect(e.InstallCNI).To(Equal(images.Image("docker.io/istio/cni-fips:1.10.0")))
Expect(e.ProxyV2).To(Equal(images.Image("docker.io/istio/proxyv2-fips:1.10.0")))
Expect(e.Ztunnel).To(Equal(images.Image("docker.io/istio/ztunnel-fips:1.10.0")))
})

It("should return an error when FIPS image environment variables are missing", func() {
_ = os.Setenv("KYMA_FIPS_MODE_ENABLED", "true")
_ = os.Unsetenv("pilot-fips")
_ = os.Unsetenv("install-cni-fips")
_ = os.Unsetenv("proxyv2-fips")
_ = os.Unsetenv("ztunnel-fips")

_, err := images.GetImages()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("environment variable \"pilot-fips\" should not be empty"))
Expect(err.Error()).To(ContainSubstring("environment variable \"install-cni-fips\" should not be empty"))
Expect(err.Error()).To(ContainSubstring("environment variable \"proxyv2-fips\" should not be empty"))
Expect(err.Error()).To(ContainSubstring("environment variable \"ztunnel-fips\" should not be empty"))
})
})

Context("when KYMA_FIPS_MODE_ENABLED is false", func() {
It("should use standard images", func() {
_ = os.Setenv("KYMA_FIPS_MODE_ENABLED", "false")
_ = os.Setenv("pilot-fips", "docker.io/istio/pilot-fips:1.10.0")
_ = os.Setenv("install-cni-fips", "docker.io/istio/cni-fips:1.10.0")
_ = os.Setenv("proxyv2-fips", "docker.io/istio/proxyv2-fips:1.10.0")
_ = os.Setenv("ztunnel-fips", "docker.io/istio/ztunnel-fips:1.10.0")

e, err := images.GetImages()
Expect(err).NotTo(HaveOccurred())
Expect(e.Pilot).To(Equal(images.Image("docker.io/istio/pilot:1.10.0")))
Expect(e.InstallCNI).To(Equal(images.Image("docker.io/istio/cni:1.10.0")))
Expect(e.ProxyV2).To(Equal(images.Image("docker.io/istio/proxyv2:1.10.0")))
Expect(e.Ztunnel).To(Equal(images.Image("docker.io/istio/ztunnel:1.10.0")))

})
})
})
7 changes: 4 additions & 3 deletions internal/images/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

const pullSecretEnvVar = "SKR_IMG_PULL_SECRET"

// MergeHubConfiguration merges the Istio hub configuration to the provided manifest.
func MergeHubConfiguration(manifest []byte, istioImagesHub string) ([]byte, error) {
// MergeRegistryAndTagConfiguration merges the Istio hub and tag configuration to the provided manifest.
func MergeRegistryAndTagConfiguration(manifest []byte, istioImagesRegistryAndTag RegistryAndTag) ([]byte, error) {
var templateMap map[string]interface{}
err := yaml.Unmarshal(manifest, &templateMap)
if err != nil {
Expand All @@ -19,7 +19,8 @@ func MergeHubConfiguration(manifest []byte, istioImagesHub string) ([]byte, erro

err = mergo.Merge(&templateMap, map[string]interface{}{
"spec": map[string]interface{}{
"hub": istioImagesHub,
"hub": istioImagesRegistryAndTag.Hub,
"tag": istioImagesRegistryAndTag.Tag,
},
}, mergo.WithOverride)
if err != nil {
Expand Down
Loading
Loading