Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce Agent TLS rolling with zarf tools update-creds agent #2065

Merged
merged 9 commits into from
Oct 13, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ zarf tools update-creds [flags]
zarf tools update-creds registry
zarf tools update-creds git
zarf tools update-creds artifact
zarf tools update-creds logging
zarf tools update-creds agent

# Update all Zarf credentials w/external services at once:
zarf tools update-creds \
Expand Down
21 changes: 16 additions & 5 deletions src/cmd/tools/zarf.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ var updateCredsCmd = &cobra.Command{
Aliases: []string{"uc"},
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
validKeys := []string{message.RegistryKey, message.GitKey, message.ArtifactKey}
validKeys := []string{message.RegistryKey, message.GitKey, message.ArtifactKey, message.AgentKey}
if len(args) == 0 {
args = validKeys
} else {
Expand Down Expand Up @@ -120,9 +120,11 @@ var updateCredsCmd = &cobra.Command{
g := git.New(oldState.GitServer)
tokenResponse, err := g.CreatePackageRegistryToken()
if err != nil {
message.Fatalf(nil, lang.CmdToolsUpdateCredsUnableCreateToken, err.Error())
// Warn if we couldn't actually update the git server (it might not be installed and we should try to continue)
message.Warnf(lang.CmdToolsUpdateCredsUnableCreateToken, err.Error())
} else {
newState.ArtifactServer.PushToken = tokenResponse.Sha1
}
newState.ArtifactServer.PushToken = tokenResponse.Sha1
}

// Save the final Zarf State
Expand All @@ -142,13 +144,22 @@ var updateCredsCmd = &cobra.Command{
if slices.Contains(args, message.RegistryKey) && newState.RegistryInfo.InternalRegistry {
err = h.UpdateZarfRegistryValues()
if err != nil {
message.Fatalf(err, lang.CmdToolsUpdateCredsUnableUpdateRegistry, err.Error())
// Warn if we couldn't actually update the registry (it might not be installed and we should try to continue)
message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateRegistry, err.Error())
}
}
if slices.Contains(args, message.GitKey) && newState.GitServer.InternalServer {
err = h.UpdateZarfGiteaValues()
if err != nil {
message.Fatalf(err, lang.CmdToolsUpdateCredsUnableUpdateGit, err.Error())
// Warn if we couldn't actually update the git server (it might not be installed and we should try to continue)
message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateGit, err.Error())
}
}
if slices.Contains(args, message.AgentKey) {
err = h.UpdateZarfAgentValues()
if err != nil {
// Warn if we couldn't actually update the agent (it might not be installed and we should try to continue)
message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateAgent, err.Error())
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/config/lang/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ $ zarf tools registry digest reg.example.com/stefanprodan/podinfo:6.4.0
zarf tools update-creds registry
zarf tools update-creds git
zarf tools update-creds artifact
zarf tools update-creds logging
zarf tools update-creds agent

# Update all Zarf credentials w/external services at once:
zarf tools update-creds \
Expand All @@ -532,8 +532,9 @@ $ zarf tools registry digest reg.example.com/stefanprodan/podinfo:6.4.0
CmdToolsUpdateCredsConfirmContinue = "Continue with these changes?"
CmdToolsUpdateCredsInvalidServiceErr = "Invalid service key specified - valid keys are: %s, %s, and %s"
CmdToolsUpdateCredsUnableCreateToken = "Unable to create the new Gitea artifact token: %s"
CmdToolsUpdateCredsUnableUpdateRegistry = "Unable to update Zarf registry: %s"
CmdToolsUpdateCredsUnableUpdateGit = "Unable to update Zarf git server: %s"
CmdToolsUpdateCredsUnableUpdateRegistry = "Unable to update Zarf Registry values: %s"
CmdToolsUpdateCredsUnableUpdateGit = "Unable to update Zarf Git Server values: %s"
CmdToolsUpdateCredsUnableUpdateAgent = "Unable to update Zarf Agent TLS secrets: %s"

// zarf version
CmdVersionShort = "Shows the version of the running Zarf binary"
Expand Down
3 changes: 3 additions & 0 deletions src/internal/cluster/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ func (c *Cluster) MergeZarfState(oldState *types.ZarfState, initOptions types.Za
newState.ArtifactServer.PushToken = ""
}
}
if slices.Contains(services, message.AgentKey) {
newState.AgentTLS = pki.GeneratePKI(config.ZarfAgentHost)
}

return &newState
}
Expand Down
2 changes: 1 addition & 1 deletion src/internal/packager/helm/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ func (h *Helm) migrateDeprecatedAPIs(latestRelease *release.Release) error {
return err
}

// Use helm to re-split the manifest byte (same call used by helm to pass this data to postRender)
// Use helm to re-split the manifest bytes (same call used by helm to pass this data to postRender)
_, resources, err := releaseutil.SortManifests(map[string]string{"manifest": latestRelease.Manifest}, nil, releaseutil.InstallOrder)

if err != nil {
Expand Down
86 changes: 86 additions & 0 deletions src/internal/packager/helm/zarf.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ package helm
import (
"fmt"

"github.com/defenseunicorns/zarf/src/internal/cluster"
"github.com/defenseunicorns/zarf/src/internal/packager/git"
"github.com/defenseunicorns/zarf/src/pkg/k8s"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/transform"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/types"
"helm.sh/helm/v3/pkg/action"
)

// UpdateZarfRegistryValues updates the Zarf registry deployment with the new state values
Expand Down Expand Up @@ -72,3 +77,84 @@ func (h *Helm) UpdateZarfGiteaValues() error {

return nil
}

// UpdateZarfAgentValues updates the Zarf agent deployment with the new state values
func (h *Helm) UpdateZarfAgentValues() error {
spinner := message.NewProgressSpinner("Gathering information to update Zarf Agent TLS")
defer spinner.Stop()

err := h.createActionConfig(cluster.ZarfNamespaceName, spinner)
if err != nil {
return fmt.Errorf("unable to initialize the K8s client: %w", err)
}

// Get the current agent image from one of its pods.
pods := h.Cluster.WaitForPodsAndContainers(k8s.PodLookup{
Namespace: cluster.ZarfNamespaceName,
Selector: "app=agent-hook",
}, nil)

var currentAgentImage transform.Image
if len(pods) > 0 && len(pods[0].Spec.Containers) > 0 {
currentAgentImage, err = transform.ParseImageRef(pods[0].Spec.Containers[0].Image)
if err != nil {
return fmt.Errorf("unable to parse current agent image reference: %w", err)
}
} else {
return fmt.Errorf("unable to get current agent pod")
}

// List the releases to find the current agent release name.
listClient := action.NewList(h.actionConfig)

releases, err := listClient.Run()
if err != nil {
return fmt.Errorf("unable to list helm releases: %w", err)
}

spinner.Success()

for _, lsRelease := range releases {
// Update the Zarf Agent release with the new values
if lsRelease.Chart.Name() == "raw-init-zarf-agent-zarf-agent" {
h.Chart = types.ZarfChart{
Namespace: "zarf",
}
h.ReleaseName = lsRelease.Name
h.Component = types.ZarfComponent{
Name: "zarf-agent",
}
h.Cfg.Pkg.Constants = []types.ZarfPackageConstant{
{
Name: "AGENT_IMAGE",
Value: currentAgentImage.Path,
},
{
Name: "AGENT_IMAGE_TAG",
Value: currentAgentImage.Tag,
},
}

err := h.UpdateReleaseValues(map[string]interface{}{})
if err != nil {
return fmt.Errorf("error updating the release values: %w", err)
}
}
}

spinner = message.NewProgressSpinner("Cleaning up Zarf Agent pods after update")
defer spinner.Stop()

// Force pods to be recreated to get the updated secret.
err = h.Cluster.DeletePods(k8s.PodLookup{
Namespace: cluster.ZarfNamespaceName,
Selector: "app=agent-hook",
})
if err != nil {
return fmt.Errorf("error recycling pods for the Zarf Agent: %w", err)
}

spinner.Success()

return nil
}
15 changes: 15 additions & 0 deletions src/pkg/k8s/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ func (k *K8s) DeletePod(namespace string, name string) error {
}
}

// DeletePods removes a collection of pods from the cluster by pod lookup.
func (k *K8s) DeletePods(target PodLookup) error {
deleteGracePeriod := int64(0)
deletePolicy := metav1.DeletePropagationForeground
return k.Clientset.CoreV1().Pods(target.Namespace).DeleteCollection(context.TODO(),
metav1.DeleteOptions{
GracePeriodSeconds: &deleteGracePeriod,
PropagationPolicy: &deletePolicy,
},
metav1.ListOptions{
LabelSelector: target.Selector,
},
)
}

// CreatePod inserts the given pod into the cluster.
func (k *K8s) CreatePod(pod *corev1.Pod) (*corev1.Pod, error) {
createOptions := metav1.CreateOptions{}
Expand Down
9 changes: 9 additions & 0 deletions src/pkg/message/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
GitReadKey = "git-readonly"
ArtifactKey = "artifact"
LoggingKey = "logging"
AgentKey = "agent"
)

// PrintCredentialTable displays credentials in a table
Expand Down Expand Up @@ -136,6 +137,14 @@ func PrintCredentialUpdates(oldState *types.ZarfState, newState *types.ZarfState
pterm.Printfln(" %s: %s", pterm.Bold.Sprint("URL Address"), compareStrings(oA.Address, nA.Address, false))
pterm.Printfln(" %s: %s", pterm.Bold.Sprint("Push Username"), compareStrings(oA.PushUsername, nA.PushUsername, false))
pterm.Printfln(" %s: %s", pterm.Bold.Sprint("Push Token"), compareStrings(oA.PushToken, nA.PushToken, true))
case AgentKey:
oT := oldState.AgentTLS
nT := newState.AgentTLS
Title("Agent TLS", "the certificates used to connect to Zarf's Agent")
pterm.Println()
pterm.Printfln(" %s: %s", pterm.Bold.Sprint("Certificate Authority"), compareStrings(string(oT.CA), string(nT.CA), true))
pterm.Printfln(" %s: %s", pterm.Bold.Sprint("Public Certificate"), compareStrings(string(oT.Cert), string(nT.Cert), true))
pterm.Printfln(" %s: %s", pterm.Bold.Sprint("Private Key"), compareStrings(string(oT.Key), string(nT.Key), true))
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/test/e2e/21_connect_creds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@ func TestConnectAndCreds(t *testing.T) {
t.Log("E2E: Connect")
e2e.SetupWithCluster(t)

prevAgentSecretData, _, err := e2e.Kubectl("get", "secret", "agent-hook-tls", "-n", "zarf", "-o", "jsonpath={.data}")
require.NoError(t, err)

connectToZarfServices(t)

stdOut, stdErr, err := e2e.Zarf("tools", "update-creds", "--confirm")
require.NoError(t, err, stdOut, stdErr)

newAgentSecretData, _, err := e2e.Kubectl("get", "secret", "agent-hook-tls", "-n", "zarf", "-o", "jsonpath={.data}")
require.NoError(t, err)
require.NotEqual(t, prevAgentSecretData, newAgentSecretData, "agent secrets should not be the same")

connectToZarfServices(t)

stdOut, stdErr, err = e2e.Zarf("package", "remove", "init", "--components=logging", "--confirm")
Expand Down
Loading