diff --git a/data/data/agent/files/usr/local/bin/common.sh b/data/data/agent/files/usr/local/bin/common.sh index 6b3ff3d1750..4c6fdd7558a 100644 --- a/data/data/agent/files/usr/local/bin/common.sh +++ b/data/data/agent/files/usr/local/bin/common.sh @@ -3,20 +3,21 @@ curl_assisted_service() { local endpoint=$1 local method=${2:-GET} - local additional_options=("${@:3}") # Capture all arguments starting from the third one + local authz=${3:-} + local additional_options=("${@:4}") # Capture all arguments starting from the fourth one local baseURL="${SERVICE_BASE_URL}api/assisted-install/v2" case "${method}" in "POST") curl -s -S -X POST "${additional_options[@]}" "${baseURL}${endpoint}" \ - -H "Authorization: ${AGENT_AUTH_TOKEN}" \ + -H "Authorization: ${authz}" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ ;; "GET") curl -s -S -X GET "${additional_options[@]}" "${baseURL}${endpoint}" \ - -H "Authorization: ${AGENT_AUTH_TOKEN}" \ + -H "Authorization: ${authz}" \ -H "Accept: application/json" ;; esac -} +} diff --git a/data/data/agent/files/usr/local/bin/start-agent.sh b/data/data/agent/files/usr/local/bin/start-agent.sh index 4efcbb3f9bb..35715b8c375 100644 --- a/data/data/agent/files/usr/local/bin/start-agent.sh +++ b/data/data/agent/files/usr/local/bin/start-agent.sh @@ -8,7 +8,7 @@ INFRA_ENV_ID="" until [[ $INFRA_ENV_ID != "" && $INFRA_ENV_ID != "null" ]]; do sleep 5 >&2 echo "Querying assisted-service for infra-env-id..." - INFRA_ENV_ID=$(curl_assisted_service "/infra-envs" GET | jq -r .[0].id) + INFRA_ENV_ID=$(curl_assisted_service "/infra-envs" GET $USER_AUTH_TOKEN | jq -r '.[0].id') done echo "Fetched infra-env-id and found: $INFRA_ENV_ID" diff --git a/data/data/agent/files/usr/local/bin/start-cluster-installation.sh b/data/data/agent/files/usr/local/bin/start-cluster-installation.sh index cc614c3cf14..dfe56791d45 100644 --- a/data/data/agent/files/usr/local/bin/start-cluster-installation.sh +++ b/data/data/agent/files/usr/local/bin/start-cluster-installation.sh @@ -10,7 +10,7 @@ cluster_id="" while [[ "${cluster_id}" = "" ]] do # Get cluster id - cluster_id=$(curl_assisted_service "/clusters" GET | jq -r .[].id) + cluster_id=$(curl_assisted_service "/clusters" GET $USER_AUTH_TOKEN | jq -r .[].id) if [[ "${cluster_id}" = "" ]]; then sleep 2 fi @@ -28,7 +28,7 @@ status_issue="90_start-install" num_known_hosts() { local known_hosts=0 local insufficient_hosts=0 - host_status=$(curl_assisted_service "/infra-envs/${INFRA_ENV_ID}/hosts" GET | jq -r .[].status) + host_status=$(curl_assisted_service "/infra-envs/${INFRA_ENV_ID}/hosts" GET $USER_AUTH_TOKEN | jq -r .[].status) if [[ -n ${host_status} ]]; then for status in ${host_status}; do if [[ "${status}" == "known" ]]; then @@ -58,7 +58,7 @@ clear_issue "${status_issue}" while [[ "${cluster_status}" != "installed" ]] do sleep 5 - cluster_info="$(curl_assisted_service "/clusters" GET)" + cluster_info="$(curl_assisted_service "/clusters" GET $USER_AUTH_TOKEN)" cluster_status=$(printf '%s' "${cluster_info}" | jq -r .[].status) echo "Cluster status: ${cluster_status}" 1>&2 # Start the cluster install, if it transitions back to Ready due to a failure, @@ -66,7 +66,7 @@ do case "${cluster_status}" in "ready") echo "Starting cluster installation..." 1>&2 - res=$(curl_assisted_service "/clusters/${cluster_id}/actions/install" POST -w "%{http_code}" -o /dev/null) + res=$(curl_assisted_service "/clusters/${cluster_id}/actions/install" POST $USER_AUTH_TOKEN -w "%{http_code}" -o /dev/null) if [[ $res = "202" ]]; then printf '\nCluster installation started\n' 1>&2 fi diff --git a/data/data/agent/files/usr/local/bin/wait-for-assisted-service.sh b/data/data/agent/files/usr/local/bin/wait-for-assisted-service.sh index d68b63835d7..711477adb26 100644 --- a/data/data/agent/files/usr/local/bin/wait-for-assisted-service.sh +++ b/data/data/agent/files/usr/local/bin/wait-for-assisted-service.sh @@ -6,7 +6,7 @@ source "common.sh" echo "Waiting for assisted-service to be ready" -until curl_assisted_service "/infra-envs" GET -o /dev/null --silent --fail; do +until curl_assisted_service "/infra-envs" GET $USER_AUTH_TOKEN -o /dev/null --silent --fail; do printf '.' sleep 5 done diff --git a/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template b/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template index d6521d008df..fc8a404b859 100644 --- a/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template +++ b/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template @@ -20,4 +20,6 @@ OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR={{.ReleaseImageMirror}} STORAGE=filesystem INFRA_ENV_ID={{.InfraEnvID}} EC_PUBLIC_KEY_PEM={{.PublicKeyPEM}} -AGENT_AUTH_TOKEN={{.Token}} +AGENT_AUTH_TOKEN={{.AgentAuthToken}} +USER_AUTH_TOKEN={{.UserAuthToken}} +WATCHER_AUTH_TOKEN={{.WatcherAuthToken}} diff --git a/data/data/agent/systemd/units/agent-auth-token-status.service b/data/data/agent/systemd/units/agent-auth-token-status.service index ea985649148..fdeec8de73a 100644 --- a/data/data/agent/systemd/units/agent-auth-token-status.service +++ b/data/data/agent/systemd/units/agent-auth-token-status.service @@ -1,5 +1,5 @@ [Unit] -Description=service that displays a message if agent auth token is expired. +Description=service that displays a message if auth tokens are expired. Wants=network-online.target After=network-online.target agent-interactive-console.service ConditionPathExists=/etc/assisted/add-nodes.env diff --git a/data/data/agent/systemd/units/agent-import-cluster.service.template b/data/data/agent/systemd/units/agent-import-cluster.service.template index 57923702b40..eeab1719c61 100644 --- a/data/data/agent/systemd/units/agent-import-cluster.service.template +++ b/data/data/agent/systemd/units/agent-import-cluster.service.template @@ -16,7 +16,7 @@ EnvironmentFile=/usr/local/share/assisted-service/assisted-service.env EnvironmentFile=/etc/assisted/add-nodes.env ExecStartPre=/bin/rm -f %t/%n.ctr-id ExecStartPre=/usr/local/bin/wait-for-assisted-service.sh -ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=agent-import-cluster -v /etc/assisted/clusterconfig:/clusterconfig -v /etc/assisted/manifests:/manifests -v /etc/assisted/extra-manifests:/extra-manifests {{ if .HaveMirrorConfig }}-v /etc/containers:/etc/containers{{ end }} {{.CaBundleMount}} --env SERVICE_BASE_URL --env OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR --env CLUSTER_ID --env CLUSTER_NAME --env CLUSTER_API_VIP_DNS_NAME --env AGENT_AUTH_TOKEN $SERVICE_IMAGE /usr/local/bin/agent-installer-client importCluster +ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=agent-import-cluster -v /etc/assisted/clusterconfig:/clusterconfig -v /etc/assisted/manifests:/manifests -v /etc/assisted/extra-manifests:/extra-manifests {{ if .HaveMirrorConfig }}-v /etc/containers:/etc/containers{{ end }} {{.CaBundleMount}} --env SERVICE_BASE_URL --env OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR --env CLUSTER_ID --env CLUSTER_NAME --env CLUSTER_API_VIP_DNS_NAME --env USER_AUTH_TOKEN $SERVICE_IMAGE /usr/local/bin/agent-installer-client importCluster ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id diff --git a/data/data/agent/systemd/units/agent-register-cluster.service.template b/data/data/agent/systemd/units/agent-register-cluster.service.template index 11c01bf2d26..1c5e400c58e 100644 --- a/data/data/agent/systemd/units/agent-register-cluster.service.template +++ b/data/data/agent/systemd/units/agent-register-cluster.service.template @@ -15,7 +15,7 @@ EnvironmentFile=/usr/local/share/assisted-service/agent-images.env EnvironmentFile=/usr/local/share/assisted-service/assisted-service.env ExecStartPre=/bin/rm -f %t/%n.ctr-id ExecStartPre=/usr/local/bin/wait-for-assisted-service.sh -ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=agent-register-cluster -v /etc/assisted/manifests:/manifests -v /etc/assisted/extra-manifests:/extra-manifests {{ if .HaveMirrorConfig }}-v /etc/containers:/etc/containers{{ end }} {{.CaBundleMount}} --env SERVICE_BASE_URL --env OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR --env AGENT_AUTH_TOKEN $SERVICE_IMAGE /usr/local/bin/agent-installer-client registerCluster +ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=agent-register-cluster -v /etc/assisted/manifests:/manifests -v /etc/assisted/extra-manifests:/extra-manifests {{ if .HaveMirrorConfig }}-v /etc/containers:/etc/containers{{ end }} {{.CaBundleMount}} --env SERVICE_BASE_URL --env OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR --env USER_AUTH_TOKEN $SERVICE_IMAGE /usr/local/bin/agent-installer-client registerCluster ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id diff --git a/data/data/agent/systemd/units/agent-register-infraenv.service.template b/data/data/agent/systemd/units/agent-register-infraenv.service.template index f6c399d9127..e4698d2a26d 100644 --- a/data/data/agent/systemd/units/agent-register-infraenv.service.template +++ b/data/data/agent/systemd/units/agent-register-infraenv.service.template @@ -12,7 +12,7 @@ EnvironmentFile=/etc/assisted/rendezvous-host.env EnvironmentFile=/usr/local/share/assisted-service/agent-images.env EnvironmentFile=/usr/local/share/assisted-service/assisted-service.env ExecStartPre=/bin/rm -f %t/%n.ctr-id -ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=agent-register-infraenv -v /etc/assisted/manifests:/manifests --env SERVICE_BASE_URL --env IMAGE_TYPE_ISO --env AGENT_AUTH_TOKEN $SERVICE_IMAGE /usr/local/bin/agent-installer-client registerInfraEnv +ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=agent-register-infraenv -v /etc/assisted/manifests:/manifests --env SERVICE_BASE_URL --env IMAGE_TYPE_ISO --env USER_AUTH_TOKEN $SERVICE_IMAGE /usr/local/bin/agent-installer-client registerInfraEnv ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id diff --git a/data/data/agent/systemd/units/apply-host-config.service b/data/data/agent/systemd/units/apply-host-config.service index a79442c39fa..1c993b20089 100644 --- a/data/data/agent/systemd/units/apply-host-config.service +++ b/data/data/agent/systemd/units/apply-host-config.service @@ -14,7 +14,7 @@ EnvironmentFile=/usr/local/share/assisted-service/assisted-service.env ExecStartPre=/bin/rm -f %t/%n.ctr-id ExecStartPre=/bin/mkdir -p %t/agent-installer /etc/assisted/hostconfig ExecStartPre=/usr/local/bin/wait-for-assisted-service.sh -ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --restart=on-failure:10 --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=apply-host-config -v /etc/assisted/hostconfig:/etc/assisted/hostconfig -v %t/agent-installer:/var/run/agent-installer:z --env SERVICE_BASE_URL --env INFRA_ENV_ID --env WORKFLOW_TYPE --env AGENT_AUTH_TOKEN $SERVICE_IMAGE /usr/local/bin/agent-installer-client configure +ExecStart=podman run --net host --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --restart=on-failure:10 --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=apply-host-config -v /etc/assisted/hostconfig:/etc/assisted/hostconfig -v %t/agent-installer:/var/run/agent-installer:z --env SERVICE_BASE_URL --env INFRA_ENV_ID --env WORKFLOW_TYPE --env USER_AUTH_TOKEN $SERVICE_IMAGE /usr/local/bin/agent-installer-client configure ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id diff --git a/pkg/agent/cluster.go b/pkg/agent/cluster.go index 8827ab65821..060b48bcb22 100644 --- a/pkg/agent/cluster.go +++ b/pkg/agent/cluster.go @@ -71,17 +71,17 @@ func NewCluster(ctx context.Context, assetDir, rendezvousIP, kubeconfigPath, ssh czero := &Cluster{} capi := &clientSet{} - var authToken string + var watcherAuthToken string var err error switch workflowType { case workflow.AgentWorkflowTypeInstall: - authToken, err = FindAuthTokenFromAssetStore(assetDir) + watcherAuthToken, err = FindAuthTokenFromAssetStore(assetDir) if err != nil { return nil, err } case workflow.AgentWorkflowTypeAddNodes: - authToken, err = gencrypto.GetAuthTokenFromCluster(ctx, kubeconfigPath) + watcherAuthToken, err = gencrypto.GetAuthTokenFromCluster(ctx, kubeconfigPath) if err != nil { return nil, err } @@ -89,7 +89,7 @@ func NewCluster(ctx context.Context, assetDir, rendezvousIP, kubeconfigPath, ssh return nil, fmt.Errorf("AgentWorkflowType value not supported: %s", workflowType) } - restclient := NewNodeZeroRestClient(ctx, rendezvousIP, sshKey, authToken) + restclient := NewNodeZeroRestClient(ctx, rendezvousIP, sshKey, watcherAuthToken) kubeclient, err := NewClusterKubeAPIClient(ctx, kubeconfigPath) if err != nil { diff --git a/pkg/agent/rest.go b/pkg/agent/rest.go index 4388f5f49f4..b264e7d1303 100644 --- a/pkg/agent/rest.go +++ b/pkg/agent/rest.go @@ -33,7 +33,7 @@ type NodeZeroRestClient struct { } // NewNodeZeroRestClient Initialize a new rest client to interact with the Agent Rest API on node zero. -func NewNodeZeroRestClient(ctx context.Context, rendezvousIP, sshKey, token string) *NodeZeroRestClient { +func NewNodeZeroRestClient(ctx context.Context, rendezvousIP, sshKey, watcherAuthToken string) *NodeZeroRestClient { restClient := &NodeZeroRestClient{} // Get SSH Keys which can be used to determine if Rest API failures are due to network connectivity issues @@ -48,7 +48,7 @@ func NewNodeZeroRestClient(ctx context.Context, rendezvousIP, sshKey, token stri Path: client.DefaultBasePath, } - config.AuthInfo = gencrypto.UserAuthHeaderWriter(token) + config.AuthInfo = gencrypto.WatcherAuthHeaderWriter(watcherAuthToken) client := client.New(config) @@ -137,7 +137,7 @@ func FindAuthTokenFromAssetStore(assetDir string) (string, error) { var authToken string if authConfig != nil { - authToken = authConfig.(*gencrypto.AuthConfig).AgentAuthToken + authToken = authConfig.(*gencrypto.AuthConfig).WatcherAuthToken } return authToken, nil diff --git a/pkg/asset/agent/gencrypto/auth_utils.go b/pkg/asset/agent/gencrypto/auth_utils.go index 13628045e9d..e28a19cf060 100644 --- a/pkg/asset/agent/gencrypto/auth_utils.go +++ b/pkg/asset/agent/gencrypto/auth_utils.go @@ -9,22 +9,32 @@ import ( "github.com/pkg/errors" ) -// UserAuthHeaderWriter sets the JWT authorization token. -func UserAuthHeaderWriter(token string) runtime.ClientAuthInfoWriter { +// WatcherAuthHeaderWriter sets the JWT authorization token. +func WatcherAuthHeaderWriter(token string) runtime.ClientAuthInfoWriter { return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { - return r.SetHeaderParam("Authorization", token) + return r.SetHeaderParam("Watcher-Authorization", token) }) } -// ParseExpirationFromToken checks if the token is expired or not. -func ParseExpirationFromToken(tokenString string) (time.Time, error) { +// ParseToken checks if the token string is valid or not and returns JWT token claim. +func ParseToken(tokenString string) (jwt.MapClaims, error) { token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) if err != nil { - return time.Time{}, err + return nil, err } claims, ok := token.Claims.(jwt.MapClaims) if !ok { - return time.Time{}, errors.Errorf("malformed token claims in url") + return nil, errors.Errorf("malformed token claims in url") + } + return claims, nil +} + + +// ParseExpirationFromToken checks if the token is expired or not. +func ParseExpirationFromToken(tokenString string) (time.Time, error) { + claims, err := ParseToken(tokenString) + if err != nil { + return time.Time{}, err } exp, ok := claims["exp"].(float64) if !ok { diff --git a/pkg/asset/agent/gencrypto/auth_utils_test.go b/pkg/asset/agent/gencrypto/auth_utils_test.go index e8c24502b8c..903b2570ff5 100644 --- a/pkg/asset/agent/gencrypto/auth_utils_test.go +++ b/pkg/asset/agent/gencrypto/auth_utils_test.go @@ -13,12 +13,12 @@ func TestParseExpirationFromToken(t *testing.T) { assert.NotEmpty(t, privateKey) assert.NoError(t, err) - tokenNoExp, err := generateToken(privateKey, nil) + tokenNoExp, err := generateToken("userAuth", privateKey, nil) assert.NotEmpty(t, tokenNoExp) assert.NoError(t, err) expiry := time.Now().UTC().Add(30 * time.Second) - tokenWithExp, err := generateToken(privateKey, &expiry) + tokenWithExp, err := generateToken("userAuth", privateKey, &expiry) assert.NotEmpty(t, tokenWithExp) assert.NoError(t, err) diff --git a/pkg/asset/agent/gencrypto/authconfig.go b/pkg/asset/agent/gencrypto/authconfig.go index 1a72b9b3b0c..5c5222c5d70 100644 --- a/pkg/asset/agent/gencrypto/authconfig.go +++ b/pkg/asset/agent/gencrypto/authconfig.go @@ -34,11 +34,16 @@ var ( ) // AuthType holds the authenticator type for agent based installer. -const AuthType = "agent-installer-local" +const ( + AuthType = "agent-installer-local" + agentPersona = "agentAuth" + userPersona = "userAuth" + watcherPersona = "watcherAuth" +) // AuthConfig is an asset that generates ECDSA public/private keys, JWT token. type AuthConfig struct { - PublicKey, AgentAuthToken, AgentAuthTokenExpiry, AuthType string + PublicKey, AgentAuthToken, UserAuthToken, WatcherAuthToken, AuthTokenExpiry, AuthType string } var _ asset.Asset = (*AuthConfig)(nil) @@ -69,23 +74,49 @@ func (a *AuthConfig) Generate(_ context.Context, dependencies asset.Parents) err switch agentWorkflow.Workflow { case workflow.AgentWorkflowTypeInstall: // Auth tokens do not expire - token, err := generateToken(privateKey, nil) + agentAuthToken, err := generateToken(agentPersona, privateKey, nil) + if err != nil { + return err + } + a.AgentAuthToken = agentAuthToken + + userAuthToken, err := generateToken(userPersona, privateKey, nil) if err != nil { return err } - a.AgentAuthToken = token + a.UserAuthToken = userAuthToken + + watcherAuthToken, err := generateToken(watcherPersona, privateKey, nil) + if err != nil { + return err + } + a.WatcherAuthToken = watcherAuthToken + case workflow.AgentWorkflowTypeAddNodes: addNodesConfig := &joiner.AddNodesConfig{} dependencies.Get(addNodesConfig) // Auth tokens expires after 48 hours expiry := time.Now().UTC().Add(48 * time.Hour) - a.AgentAuthTokenExpiry = expiry.Format(time.RFC3339) - token, err := generateToken(privateKey, &expiry) + a.AuthTokenExpiry = expiry.Format(time.RFC3339) + + agentAuthToken, err := generateToken(agentPersona, privateKey, &expiry) if err != nil { return err } - a.AgentAuthToken = token + a.AgentAuthToken = agentAuthToken + + userAuthToken, err := generateToken(userPersona, privateKey, &expiry) + if err != nil { + return err + } + a.UserAuthToken = userAuthToken + + watcherAuthToken, err := generateToken(watcherPersona, privateKey, &expiry) + if err != nil { + return err + } + a.WatcherAuthToken = watcherAuthToken err = a.createOrUpdateAuthTokenSecret(addNodesConfig.Params.Kubeconfig) if err != nil { @@ -147,10 +178,11 @@ func keyPairPEM() (string, string, error) { } // generateToken returns a JWT token based on the private key. -func generateToken(privateKkeyPem string, expiry *time.Time) (string, error) { +func generateToken(userPersona string, privateKeyPem string, expiry *time.Time) (string, error) { // Create the JWT claims - claims := jwt.MapClaims{} - + claims := jwt.MapClaims{ + "sub": userPersona, + } // Set the expiry time if provided if expiry != nil { claims["exp"] = expiry.Unix() @@ -159,7 +191,7 @@ func generateToken(privateKkeyPem string, expiry *time.Time) (string, error) { // Create the token using the ES256 signing method and the claims token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) - priv, err := jwt.ParseECPrivateKeyFromPEM([]byte(privateKkeyPem)) + priv, err := jwt.ParseECPrivateKeyFromPEM([]byte(privateKeyPem)) if err != nil { return "", err } @@ -229,7 +261,7 @@ func (a *AuthConfig) createOrUpdateAuthTokenSecret(kubeconfigPath string) error // Update the token in asset store with the retrieved token from the cluster a.AgentAuthToken = retrievedToken // get the token expiry time of the retrieved token from the cluster - a.AgentAuthTokenExpiry = expiryTime.UTC().Format(time.RFC3339) + a.AuthTokenExpiry = expiryTime.UTC().Format(time.RFC3339) retrievedPublicKey, err := extractPublicKeyFromSecret(retrievedSecret) if err != nil { @@ -237,7 +269,7 @@ func (a *AuthConfig) createOrUpdateAuthTokenSecret(kubeconfigPath string) error } // Update the asset store with the retrieved public key associated with the valid token from the cluster a.PublicKey = retrievedPublicKey - logrus.Infof("Reusing existing auth token (valid up to %s)", a.AgentAuthTokenExpiry) + logrus.Infof("Reusing existing auth token (valid up to %s)", a.AuthTokenExpiry) } return err } @@ -250,7 +282,7 @@ func (a *AuthConfig) createSecret(k8sclientset kubernetes.Interface) error { // only for informational purposes Annotations: map[string]string{ "updatedAt": "", // Initially set to empty - "expiresAt": a.AgentAuthTokenExpiry, + "expiresAt": a.AuthTokenExpiry, }, }, Type: corev1.SecretTypeOpaque, @@ -263,7 +295,7 @@ func (a *AuthConfig) createSecret(k8sclientset kubernetes.Interface) error { if err != nil { return fmt.Errorf("failed to create secret: %w", err) } - logrus.Infof("Generated auth token (valid up to %s)", a.AgentAuthTokenExpiry) + logrus.Infof("Generated auth token (valid up to %s)", a.AuthTokenExpiry) logrus.Infof("Created secret %s/%s", authTokenSecretNamespace, authTokenSecretName) return nil @@ -274,13 +306,13 @@ func (a *AuthConfig) refreshAuthTokenSecret(k8sclientset kubernetes.Interface, r retrievedSecret.Data[authTokenPublicDataKey] = []byte(a.PublicKey) // only for informational purposes retrievedSecret.Annotations["updatedAt"] = time.Now().UTC().Format(time.RFC3339) - retrievedSecret.Annotations["expiresAt"] = a.AgentAuthTokenExpiry + retrievedSecret.Annotations["expiresAt"] = a.AuthTokenExpiry _, err := k8sclientset.CoreV1().Secrets(authTokenSecretNamespace).Update(context.TODO(), retrievedSecret, metav1.UpdateOptions{}) if err != nil { return err } - logrus.Infof("Auth token regenerated (valid up to %s)", a.AgentAuthTokenExpiry) + logrus.Infof("Auth token regenerated (valid up to %s)", a.AuthTokenExpiry) logrus.Infof("Updated secret %s/%s", authTokenSecretNamespace, authTokenSecretName) return nil } diff --git a/pkg/asset/agent/gencrypto/authconfig_test.go b/pkg/asset/agent/gencrypto/authconfig_test.go index 0bb1aec08e2..7a0fe032df8 100644 --- a/pkg/asset/agent/gencrypto/authconfig_test.go +++ b/pkg/asset/agent/gencrypto/authconfig_test.go @@ -31,6 +31,28 @@ func TestAuthConfig_Generate(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, authConfigAsset.PublicKey) assert.NotEmpty(t, authConfigAsset.AgentAuthToken) + + // All the 3 tokens should be unique + assert.NotEqual(t, authConfigAsset.AgentAuthToken, authConfigAsset.UserAuthToken) + assert.NotEqual(t, authConfigAsset.AgentAuthToken, authConfigAsset.WatcherAuthToken) + assert.NotEqual(t, authConfigAsset.UserAuthToken, authConfigAsset.WatcherAuthToken) + + // verify each token is signed with correct persona + claims , err := ParseToken(authConfigAsset.AgentAuthToken) + assert.NoError(t, err) + persona, _ := claims["sub"].(string) + assert.Equal(t, persona, agentPersona) + + claims , err = ParseToken(authConfigAsset.UserAuthToken) + assert.NoError(t, err) + persona, _ = claims["sub"].(string) + assert.Equal(t, persona, userPersona) + + claims , err = ParseToken(authConfigAsset.WatcherAuthToken) + assert.NoError(t, err) + persona, _ = claims["sub"].(string) + assert.Equal(t, persona, watcherPersona) + }) } } diff --git a/pkg/asset/agent/image/agentimage.go b/pkg/asset/agent/image/agentimage.go index dea9237a2cd..3bcdacdab05 100644 --- a/pkg/asset/agent/image/agentimage.go +++ b/pkg/asset/agent/image/agentimage.go @@ -74,7 +74,7 @@ func (a *AgentImage) Generate(ctx context.Context, dependencies asset.Parents) e a.platform = clusterInfo.PlatformType a.isoFilename = agentAddNodesISOFilename - a.imageExpiresAt = authConfig.AgentAuthTokenExpiry + a.imageExpiresAt = authConfig.AuthTokenExpiry default: return fmt.Errorf("AgentWorkflowType value not supported: %s", agentWorkflow.Workflow) diff --git a/pkg/asset/agent/image/ignition.go b/pkg/asset/agent/image/ignition.go index 2d1cbd84f53..1b24f887a4e 100644 --- a/pkg/asset/agent/image/ignition.go +++ b/pkg/asset/agent/image/ignition.go @@ -75,7 +75,9 @@ type agentTemplateData struct { ConfigImageFiles string ImageTypeISO string PublicKeyPEM string - Token string + AgentAuthToken string + UserAuthToken string + WatcherAuthToken string TokenExpiry string AuthType string CaBundleMount string @@ -193,7 +195,7 @@ func (a *Ignition) Generate(_ context.Context, dependencies asset.Parents) error // Enable add-nodes specific services enabledServices = append(enabledServices, "agent-add-node.service") // Generate add-nodes.env file - addNodesEnvFile := ignition.FileFromString(addNodesEnvPath, "root", 0644, getAddNodesEnv(*clusterInfo, authConfig.AgentAuthTokenExpiry)) + addNodesEnvFile := ignition.FileFromString(addNodesEnvPath, "root", 0644, getAddNodesEnv(*clusterInfo, authConfig.AuthTokenExpiry)) config.Storage.Files = append(config.Storage.Files, addNodesEnvFile) // Enable auth token service @@ -266,7 +268,9 @@ func (a *Ignition) Generate(_ context.Context, dependencies asset.Parents) error authConfig.PublicKey, authConfig.AuthType, authConfig.AgentAuthToken, - authConfig.AgentAuthTokenExpiry, + authConfig.UserAuthToken, + authConfig.WatcherAuthToken, + authConfig.AuthTokenExpiry, caBundleMount, len(registriesConfig.MirrorConfig) > 0, numMasters, numWorkers, @@ -281,7 +285,7 @@ func (a *Ignition) Generate(_ context.Context, dependencies asset.Parents) error rendezvousHostFile := ignition.FileFromString(rendezvousHostEnvPath, "root", 0644, - getRendezvousHostEnv(agentTemplateData.ServiceProtocol, a.RendezvousIP, authConfig.AgentAuthToken, agentWorkflow.Workflow)) + getRendezvousHostEnv(agentTemplateData.ServiceProtocol, a.RendezvousIP, authConfig.AgentAuthToken, authConfig.UserAuthToken, agentWorkflow.Workflow)) config.Storage.Files = append(config.Storage.Files, rendezvousHostFile) err = addBootstrapScripts(&config, agentManifests.ClusterImageSet.Spec.ReleaseImage) @@ -382,7 +386,7 @@ func addBootstrapScripts(config *igntypes.Config, releaseImage string) (err erro } func getTemplateData(name, pullSecret, releaseImageList, releaseImage, releaseImageMirror, publicContainerRegistries, - imageTypeISO, infraEnvID, publicKey, authType, token, tokenExpiry, caBundleMount string, + imageTypeISO, infraEnvID, publicKey, authType, agentAuthToken, userAuthToken, watcherAuthToken, tokenExpiry, caBundleMount string, haveMirrorConfig bool, numMasters, numWorkers int, osImage *models.OsImage, @@ -404,13 +408,15 @@ func getTemplateData(name, pullSecret, releaseImageList, releaseImage, releaseIm ImageTypeISO: imageTypeISO, PublicKeyPEM: publicKey, AuthType: authType, - Token: token, + AgentAuthToken: agentAuthToken, + UserAuthToken: userAuthToken, + WatcherAuthToken: watcherAuthToken, TokenExpiry: tokenExpiry, CaBundleMount: caBundleMount, } } -func getRendezvousHostEnv(serviceProtocol, nodeZeroIP, token string, workflowType workflow.AgentWorkflowType) string { +func getRendezvousHostEnv(serviceProtocol, nodeZeroIP, agentAuthtoken, userAuthToken string, workflowType workflow.AgentWorkflowType) string { serviceBaseURL := url.URL{ Scheme: serviceProtocol, Host: net.JoinHostPort(nodeZeroIP, "8090"), @@ -421,8 +427,10 @@ func getRendezvousHostEnv(serviceProtocol, nodeZeroIP, token string, workflowTyp Host: net.JoinHostPort(nodeZeroIP, "8888"), Path: "/", } - // AGENT_AUTH_TOKEN is required to authenticate API requests against agent-installer-local auth type. - // PULL_SECRET_TOKEN contains the same value as AGENT_AUTH_TOKEN. The name PULL_SECRET_TOKEN is used in + // USER_AUTH_TOKEN is required to authenticate API requests against agent-installer-local auth type + // and for the endpoints marked with userAuth security definition in assisted-service swagger.yaml. + // PULL_SECRET_TOKEN contains the AGENT_AUTH_TOKEN and is required for the endpoints marked with agentAuth security definition in assisted-service swagger.yaml. + // The name PULL_SECRET_TOKEN is used in // assisted-installer-agent, which is responsible for authenticating API requests related to agents. // Historically, PULL_SECRET_TOKEN was used solely to store the pull secrets. // However, as the authentication mechanisms have evolved, PULL_SECRET_TOKEN now @@ -434,10 +442,10 @@ func getRendezvousHostEnv(serviceProtocol, nodeZeroIP, token string, workflowTyp return fmt.Sprintf(`NODE_ZERO_IP=%s SERVICE_BASE_URL=%s IMAGE_SERVICE_BASE_URL=%s -AGENT_AUTH_TOKEN=%s PULL_SECRET_TOKEN=%s +USER_AUTH_TOKEN=%s WORKFLOW_TYPE=%s -`, nodeZeroIP, serviceBaseURL.String(), imageServiceBaseURL.String(), token, token, workflowType) +`, nodeZeroIP, serviceBaseURL.String(), imageServiceBaseURL.String(), agentAuthtoken, userAuthToken, workflowType) } func getAddNodesEnv(clusterInfo joiner.ClusterInfo, authTokenExpiry string) string {