openstack: move credentials to instance user data

This commit is contained in:
Malte Poll 2024-03-08 14:16:34 +01:00
parent 7fb2a357d9
commit 923a41ba01
3 changed files with 79 additions and 41 deletions

View File

@ -23,6 +23,7 @@ import (
const ( const (
imdsMetaDataURL = "http://169.254.169.254/openstack/2018-08-27/meta_data.json" imdsMetaDataURL = "http://169.254.169.254/openstack/2018-08-27/meta_data.json"
imdsUserDataURL = "http://169.254.169.254/openstack/2018-08-27/user_data"
ec2ImdsBaseURL = "http://169.254.169.254/1.0/meta-data" ec2ImdsBaseURL = "http://169.254.169.254/1.0/meta-data"
maxCacheAge = 12 * time.Hour maxCacheAge = 12 * time.Hour
) )
@ -33,6 +34,7 @@ type imdsClient struct {
vpcIPCache string vpcIPCache string
vpcIPCacheTime time.Time vpcIPCacheTime time.Time
cache metadataResponse cache metadataResponse
userDataCache userDataResponse
cacheTime time.Time cacheTime time.Time
} }
@ -129,73 +131,73 @@ func (c *imdsClient) role(ctx context.Context) (role.Role, error) {
} }
func (c *imdsClient) loadBalancerEndpoint(ctx context.Context) (string, error) { func (c *imdsClient) loadBalancerEndpoint(ctx context.Context) (string, error) {
if c.timeForUpdate(c.cacheTime) || c.cache.Tags.LoadBalancerEndpoint == "" { if c.timeForUpdate(c.cacheTime) || c.userDataCache.LoadBalancerEndpoint == "" {
if err := c.update(ctx); err != nil { if err := c.update(ctx); err != nil {
return "", err return "", err
} }
} }
if c.cache.Tags.LoadBalancerEndpoint == "" { if c.userDataCache.LoadBalancerEndpoint == "" {
return "", errors.New("unable to get load balancer endpoint") return "", errors.New("unable to get load balancer endpoint")
} }
return c.cache.Tags.LoadBalancerEndpoint, nil return c.userDataCache.LoadBalancerEndpoint, nil
} }
func (c *imdsClient) authURL(ctx context.Context) (string, error) { func (c *imdsClient) authURL(ctx context.Context) (string, error) {
if c.timeForUpdate(c.cacheTime) || c.cache.Tags.AuthURL == "" { if c.timeForUpdate(c.cacheTime) || c.userDataCache.AuthURL == "" {
if err := c.update(ctx); err != nil { if err := c.update(ctx); err != nil {
return "", err return "", err
} }
} }
if c.cache.Tags.AuthURL == "" { if c.userDataCache.AuthURL == "" {
return "", errors.New("unable to get auth url") return "", errors.New("unable to get auth url")
} }
return c.cache.Tags.AuthURL, nil return c.userDataCache.AuthURL, nil
} }
func (c *imdsClient) userDomainName(ctx context.Context) (string, error) { func (c *imdsClient) userDomainName(ctx context.Context) (string, error) {
if c.timeForUpdate(c.cacheTime) || c.cache.Tags.UserDomainName == "" { if c.timeForUpdate(c.cacheTime) || c.userDataCache.UserDomainName == "" {
if err := c.update(ctx); err != nil { if err := c.update(ctx); err != nil {
return "", err return "", err
} }
} }
if c.cache.Tags.UserDomainName == "" { if c.userDataCache.UserDomainName == "" {
return "", errors.New("unable to get user domain name") return "", errors.New("unable to get user domain name")
} }
return c.cache.Tags.UserDomainName, nil return c.userDataCache.UserDomainName, nil
} }
func (c *imdsClient) username(ctx context.Context) (string, error) { func (c *imdsClient) username(ctx context.Context) (string, error) {
if c.timeForUpdate(c.cacheTime) || c.cache.Tags.Username == "" { if c.timeForUpdate(c.cacheTime) || c.userDataCache.Username == "" {
if err := c.update(ctx); err != nil { if err := c.update(ctx); err != nil {
return "", err return "", err
} }
} }
if c.cache.Tags.Username == "" { if c.userDataCache.Username == "" {
return "", errors.New("unable to get token name") return "", errors.New("unable to get token name")
} }
return c.cache.Tags.Username, nil return c.userDataCache.Username, nil
} }
func (c *imdsClient) password(ctx context.Context) (string, error) { func (c *imdsClient) password(ctx context.Context) (string, error) {
if c.timeForUpdate(c.cacheTime) || c.cache.Tags.Password == "" { if c.timeForUpdate(c.cacheTime) || c.userDataCache.Password == "" {
if err := c.update(ctx); err != nil { if err := c.update(ctx); err != nil {
return "", err return "", err
} }
} }
if c.cache.Tags.Password == "" { if c.userDataCache.Password == "" {
return "", errors.New("unable to get token password") return "", errors.New("unable to get token password")
} }
return c.cache.Tags.Password, nil return c.userDataCache.Password, nil
} }
// timeForUpdate checks whether an update is needed due to cache age. // timeForUpdate checks whether an update is needed due to cache age.
@ -203,18 +205,41 @@ func (c *imdsClient) timeForUpdate(t time.Time) bool {
return time.Since(t) > maxCacheAge return time.Since(t) > maxCacheAge
} }
// update updates instance metadata from the azure imds API.
func (c *imdsClient) update(ctx context.Context) error { func (c *imdsClient) update(ctx context.Context) error {
if err := c.updateInstanceMetadata(ctx); err != nil {
return fmt.Errorf("updating instance metadata: %w", err)
}
if err := c.updateUserData(ctx); err != nil {
return fmt.Errorf("updating user data: %w", err)
}
c.cacheTime = time.Now()
return nil
}
// update updates instance metadata from the azure imds API.
func (c *imdsClient) updateInstanceMetadata(ctx context.Context) error {
resp, err := httpGet(ctx, c.client, imdsMetaDataURL) resp, err := httpGet(ctx, c.client, imdsMetaDataURL)
if err != nil { if err != nil {
return err return err
} }
var metadataResp metadataResponse var metadataResp metadataResponse
if err := json.Unmarshal(resp, &metadataResp); err != nil { if err := json.Unmarshal(resp, &metadataResp); err != nil {
return fmt.Errorf("unmarshalling IMDS metadata response %q: %w", string(resp), err) return fmt.Errorf("unmarshalling IMDS metadata response %q: %w", resp, err)
} }
c.cache = metadataResp c.cache = metadataResp
c.cacheTime = time.Now() return nil
}
func (c *imdsClient) updateUserData(ctx context.Context) error {
resp, err := httpGet(ctx, c.client, imdsUserDataURL)
if err != nil {
return err
}
var userdataResp userDataResponse
if err := json.Unmarshal(resp, &userdataResp); err != nil {
return fmt.Errorf("unmarshalling IMDS user_data response %q: %w", resp, err)
}
c.userDataCache = userdataResp
return nil return nil
} }
@ -262,9 +287,12 @@ type metadataResponse struct {
} }
type metadataTags struct { type metadataTags struct {
InitSecretHash string `json:"constellation-init-secret-hash,omitempty"` InitSecretHash string `json:"constellation-init-secret-hash,omitempty"`
Role string `json:"constellation-role,omitempty"` Role string `json:"constellation-role,omitempty"`
UID string `json:"constellation-uid,omitempty"` UID string `json:"constellation-uid,omitempty"`
}
type userDataResponse struct {
AuthURL string `json:"openstack-auth-url,omitempty"` AuthURL string `json:"openstack-auth-url,omitempty"`
UserDomainName string `json:"openstack-user-domain-name,omitempty"` UserDomainName string `json:"openstack-user-domain-name,omitempty"`
Username string `json:"openstack-username,omitempty"` Username string `json:"openstack-username,omitempty"`

View File

@ -26,7 +26,7 @@ func TestProviderID(t *testing.T) {
someErr := errors.New("failed") someErr := errors.New("failed")
type testCase struct { type testCase struct {
cache metadataResponse cache any
cacheTime time.Time cacheTime time.Time
newClient httpClientJSONCreateFunc newClient httpClientJSONCreateFunc
wantResult string wantResult string
@ -34,7 +34,7 @@ func TestProviderID(t *testing.T) {
wantErr bool wantErr bool
} }
newTestCases := func(mResp1, mResp2 metadataResponse, expect1, expect2 string) map[string]testCase { newTestCases := func(mResp1, mResp2 any, expect1, expect2 string) map[string]testCase {
return map[string]testCase{ return map[string]testCase{
"cached": { "cached": {
cache: mResp1, cache: mResp1,
@ -120,32 +120,32 @@ func TestProviderID(t *testing.T) {
"authURL": { "authURL": {
method: (*imdsClient).authURL, method: (*imdsClient).authURL,
testCases: newTestCases( testCases: newTestCases(
metadataResponse{Tags: metadataTags{AuthURL: "authURL1"}}, userDataResponse{AuthURL: "authURL1"},
metadataResponse{Tags: metadataTags{AuthURL: "authURL2"}}, userDataResponse{AuthURL: "authURL2"},
"authURL1", "authURL2", "authURL1", "authURL2",
), ),
}, },
"userDomainName": { "userDomainName": {
method: (*imdsClient).userDomainName, method: (*imdsClient).userDomainName,
testCases: newTestCases( testCases: newTestCases(
metadataResponse{Tags: metadataTags{UserDomainName: "userDomainName1"}}, userDataResponse{UserDomainName: "userDomainName1"},
metadataResponse{Tags: metadataTags{UserDomainName: "userDomainName2"}}, userDataResponse{UserDomainName: "userDomainName2"},
"userDomainName1", "userDomainName2", "userDomainName1", "userDomainName2",
), ),
}, },
"username": { "username": {
method: (*imdsClient).username, method: (*imdsClient).username,
testCases: newTestCases( testCases: newTestCases(
metadataResponse{Tags: metadataTags{Username: "username1"}}, userDataResponse{Username: "username1"},
metadataResponse{Tags: metadataTags{Username: "username2"}}, userDataResponse{Username: "username2"},
"username1", "username2", "username1", "username2",
), ),
}, },
"password": { "password": {
method: (*imdsClient).password, method: (*imdsClient).password,
testCases: newTestCases( testCases: newTestCases(
metadataResponse{Tags: metadataTags{Password: "password1"}}, userDataResponse{Password: "password1"},
metadataResponse{Tags: metadataTags{Password: "password2"}}, userDataResponse{Password: "password2"},
"password1", "password2", "password1", "password2",
), ),
}, },
@ -162,10 +162,18 @@ func TestProviderID(t *testing.T) {
if tc.newClient != nil { if tc.newClient != nil {
client = tc.newClient(require) client = tc.newClient(require)
} }
var cache metadataResponse
var userDataCache userDataResponse
if _, ok := tc.cache.(metadataResponse); ok {
cache = tc.cache.(metadataResponse)
} else if _, ok := tc.cache.(userDataResponse); ok {
userDataCache = tc.cache.(userDataResponse)
}
imds := &imdsClient{ imds := &imdsClient{
client: client, client: client,
cache: tc.cache, cache: cache,
cacheTime: tc.cacheTime, userDataCache: userDataCache,
cacheTime: tc.cacheTime,
} }
result, err := tu.method(imds, context.Background()) result, err := tu.method(imds, context.Background())
@ -373,13 +381,13 @@ type httpClientJSONCreateFunc func(r *require.Assertions) *stubHTTPClientJSON
type stubHTTPClientJSON struct { type stubHTTPClientJSON struct {
require *require.Assertions require *require.Assertions
response metadataResponse response any
code int code int
err error err error
called bool called bool
} }
func newStubHTTPClientJSONFunc(response metadataResponse, statusCode int, err error) httpClientJSONCreateFunc { func newStubHTTPClientJSONFunc(response any, statusCode int, err error) httpClientJSONCreateFunc {
return func(r *require.Assertions) *stubHTTPClientJSON { return func(r *require.Assertions) *stubHTTPClientJSON {
return &stubHTTPClientJSON{ return &stubHTTPClientJSON{
response: response, response: response,

View File

@ -72,14 +72,16 @@ resource "openstack_compute_instance_v2" "instance_group_member" {
delete_on_termination = true delete_on_termination = true
} }
metadata = { metadata = {
constellation-role = var.role constellation-role = var.role
constellation-uid = var.uid constellation-uid = var.uid
constellation-init-secret-hash = var.init_secret_hash constellation-init-secret-hash = var.init_secret_hash
}
user_data = jsonencode({
openstack-auth-url = var.identity_internal_url openstack-auth-url = var.identity_internal_url
openstack-username = var.openstack_username openstack-username = var.openstack_username
openstack-password = var.openstack_password openstack-password = var.openstack_password
openstack-user-domain-name = var.openstack_user_domain_name openstack-user-domain-name = var.openstack_user_domain_name
openstack-load-balancer-endpoint = var.openstack_load_balancer_endpoint openstack-load-balancer-endpoint = var.openstack_load_balancer_endpoint
} })
availability_zone_hints = var.availability_zone availability_zone_hints = length(var.availability_zone) > 0 ? var.availability_zone : null
} }