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

AGENT-950: Implement Separate JWT Tokens for Different User Personas #9039

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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 data/data/agent/files/usr/local/bin/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion data/data/agent/files/usr/local/bin/start-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -58,15 +58,15 @@ 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,
# then it will be restarted
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion data/data/agent/systemd/units/apply-host-config.service
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions pkg/agent/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,25 @@ 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
}
default:
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 {
Expand Down
6 changes: 3 additions & 3 deletions pkg/agent/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) // this is used only by wait-for so set watcher auth alone

client := client.New(config)

Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions pkg/asset/agent/gencrypto/auth_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ 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)
})
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/asset/agent/gencrypto/auth_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
59 changes: 43 additions & 16 deletions pkg/asset/agent/gencrypto/authconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const AuthType = "agent-installer-local"

// 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)
Expand Down Expand Up @@ -69,23 +69,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("agentAuth", privateKey, nil)
if err != nil {
return err
}
a.AgentAuthToken = token
a.AgentAuthToken = agentAuthToken

userAuthToken, err := generateToken("userAuth", privateKey, nil)
if err != nil {
return err
}
a.UserAuthToken = userAuthToken

watcherAuthToken, err := generateToken("watcherAuth", 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("agentAuth", privateKey, &expiry)
if err != nil {
return err
}
a.AgentAuthToken = token
a.AgentAuthToken = agentAuthToken

userAuthToken, err := generateToken("userAuth", privateKey, &expiry)
if err != nil {
return err
}
a.UserAuthToken = userAuthToken

watcherAuthToken, err := generateToken("watcherAuth", privateKey, &expiry)
if err != nil {
return err
}
a.WatcherAuthToken = watcherAuthToken

err = a.createOrUpdateAuthTokenSecret(addNodesConfig.Params.Kubeconfig)
if err != nil {
Expand Down Expand Up @@ -147,10 +173,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()
Expand All @@ -159,7 +186,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
}
Expand Down Expand Up @@ -229,15 +256,15 @@ 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 {
return err
}
// 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
}
Expand All @@ -250,7 +277,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,
Expand All @@ -263,7 +290,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
Expand All @@ -274,13 +301,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
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/asset/agent/image/agentimage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading