Skip to content

Commit c5048c1

Browse files
AGENT-919: Authenticate day2 operations
Creating a Nodes ISO: - secret does not exist 1. Generate a new public key and JWT token with an expiration time of 48 hours 2. The public key and token gets saved into the asset store 3. Create a secret named agent-auth-token in the openshift-config namespace with this token and public key from the asset store. - secret already exists 1. Retrieve the stored token and check if the secret with JWT token is older than 24 hours. - secret already exists and the token is older than 24 Hours 1. Generate a new public key and JWT token with a new expiration time of 48 hours 2. Update the secret with a new public key and JWT token - secret already exists and the token is not older than 24 Hours 1. Retrieve the token and public key from the secret and update the asset store with the values from the secret. Running monitor-add-nodes Command: 1. Retrieve the token from the secret. 2. Register the agent installer client with the retrieved token. 3. Send the auth token to the assisted service API via HTTP headers
1 parent 8e548c3 commit c5048c1

File tree

10 files changed

+321
-29
lines changed

10 files changed

+321
-29
lines changed

data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR={{.ReleaseImageMirror}}
1919
STORAGE=filesystem
2020
INFRA_ENV_ID={{.InfraEnvID}}
2121
EC_PUBLIC_KEY_PEM={{.PublicKeyPEM}}
22-
AGENT_AUTH_TOKEN={{.Token}}
22+
AGENT_AUTH_TOKEN={{.Token}}

data/data/agent/systemd/units/agent-import-cluster.service.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ EnvironmentFile=/usr/local/share/assisted-service/assisted-service.env
1616
EnvironmentFile=/etc/assisted/add-nodes.env
1717
ExecStartPre=/bin/rm -f %t/%n.ctr-id
1818
ExecStartPre=/usr/local/bin/wait-for-assisted-service.sh
19-
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 $SERVICE_IMAGE /usr/local/bin/agent-installer-client importCluster
19+
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
2020
ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
2121
ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id
2222

pkg/agent/cluster.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/openshift/assisted-service/client/installer"
1717
"github.com/openshift/assisted-service/models"
18+
"github.com/openshift/installer/pkg/asset/agent/gencrypto"
1819
"github.com/openshift/installer/pkg/asset/agent/workflow"
1920
"github.com/openshift/installer/pkg/gather/ssh"
2021
)
@@ -70,9 +71,22 @@ func NewCluster(ctx context.Context, assetDir, rendezvousIP, kubeconfigPath, ssh
7071
czero := &Cluster{}
7172
capi := &clientSet{}
7273

73-
authToken, err := FindAuthTokenFromAssetStore(assetDir)
74-
if err != nil {
75-
logrus.Fatal(err)
74+
var authToken string
75+
var err error
76+
77+
switch workflowType {
78+
case workflow.AgentWorkflowTypeInstall:
79+
authToken, err = FindAuthTokenFromAssetStore(assetDir)
80+
if err != nil {
81+
return nil, err
82+
}
83+
case workflow.AgentWorkflowTypeAddNodes:
84+
authToken, err = gencrypto.GetAuthTokenFromCluster(ctx, kubeconfigPath)
85+
if err != nil {
86+
return nil, err
87+
}
88+
default:
89+
return nil, fmt.Errorf("AgentWorkflowType value not supported: %s", workflowType)
7690
}
7791

7892
restclient := NewNodeZeroRestClient(ctx, rendezvousIP, sshKey, authToken)

pkg/agent/rest.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func FindRendezvouIPAndSSHKeyFromAssetStore(assetDir string) (string, string, er
119119
return rendezvousIP, sshKey, nil
120120
}
121121

122-
// FindAuthTokenFromAssetStore returns the auth token.
122+
// FindAuthTokenFromAssetStore returns the auth token from asset store.
123123
func FindAuthTokenFromAssetStore(assetDir string) (string, error) {
124124
authConfigAsset := &gencrypto.AuthConfig{}
125125

@@ -135,9 +135,12 @@ func FindAuthTokenFromAssetStore(assetDir string) (string, error) {
135135
return "", errors.New("failed to load AuthConfig")
136136
}
137137

138-
token := authConfig.(*gencrypto.AuthConfig).Token
138+
var authToken string
139+
if authConfig != nil {
140+
authToken = authConfig.(*gencrypto.AuthConfig).AgentAuthToken
141+
}
139142

140-
return token, nil
143+
return authToken, nil
141144
}
142145

143146
// IsRestAPILive Determine if the Agent Rest API on node zero has initialized

pkg/asset/agent/gencrypto/auth_utils.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package gencrypto
22

33
import (
4+
"time"
5+
46
"github.com/go-openapi/runtime"
57
"github.com/go-openapi/strfmt"
8+
"github.com/golang-jwt/jwt/v4"
9+
"github.com/pkg/errors"
610
)
711

812
// UserAuthHeaderWriter sets the JWT authorization token.
@@ -11,3 +15,24 @@ func UserAuthHeaderWriter(token string) runtime.ClientAuthInfoWriter {
1115
return r.SetHeaderParam("Authorization", token)
1216
})
1317
}
18+
19+
// ParseExpirationFromToken checks if the token is expired or not.
20+
func ParseExpirationFromToken(tokenString string) (time.Time, error) {
21+
token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{})
22+
if err != nil {
23+
return time.Time{}, err
24+
}
25+
claims, ok := token.Claims.(jwt.MapClaims)
26+
if !ok {
27+
return time.Time{}, errors.Errorf("malformed token claims in url")
28+
}
29+
exp, ok := claims["exp"].(float64)
30+
if !ok {
31+
return time.Time{}, errors.Errorf("token missing 'exp' claim")
32+
}
33+
expTime := time.Unix(int64(exp), 0)
34+
expiresAt := strfmt.DateTime(expTime)
35+
expiryTime := time.Time(expiresAt)
36+
37+
return expiryTime, nil
38+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package gencrypto
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/uuid"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestParseExpirationFromToken(t *testing.T) {
12+
publicKey, privateKey, err := keyPairPEM()
13+
assert.NotEmpty(t, publicKey)
14+
assert.NotEmpty(t, privateKey)
15+
assert.NoError(t, err)
16+
17+
infraEnvID := uuid.New().String()
18+
19+
tokenNoExp, err := generateToken(infraEnvID, privateKey)
20+
assert.NotEmpty(t, tokenNoExp)
21+
assert.NoError(t, err)
22+
23+
expiry := time.Now().UTC().Add(30 * time.Second)
24+
tokenWithExp, err := generateToken(infraEnvID, privateKey, expiry)
25+
assert.NotEmpty(t, tokenWithExp)
26+
assert.NoError(t, err)
27+
28+
cases := []struct {
29+
name, token, errorMessage string
30+
expectedErr bool
31+
}{
32+
{
33+
name: "exp-claim-not-set",
34+
token: tokenNoExp,
35+
expectedErr: true,
36+
errorMessage: "token missing 'exp' claim",
37+
},
38+
{
39+
name: "exp-claim-set",
40+
token: tokenWithExp,
41+
},
42+
}
43+
for _, tc := range cases {
44+
t.Run(tc.name, func(t *testing.T) {
45+
_, err = ParseExpirationFromToken(tc.token)
46+
if tc.expectedErr {
47+
assert.EqualError(t, err, tc.errorMessage)
48+
} else {
49+
assert.NoError(t, err)
50+
}
51+
})
52+
}
53+
}

0 commit comments

Comments
 (0)