Skip to content
Merged
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
64 changes: 35 additions & 29 deletions bin/ensureServiceUser
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@
// - deduplicate with Vault's seed script at https://github.com/scality/Vault/pull/1627
// - add permission boundaries to user when https://scality.atlassian.net/browse/VAULT-4 is implemented

process.env.AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE = '1';
const { errors } = require('arsenal');
const { program } = require('commander');
const werelogs = require('werelogs');
const async = require('async');
const { IAM } = require('aws-sdk');
const {
IAMClient,
AttachUserPolicyCommand,
CreateAccessKeyCommand,
CreatePolicyCommand,
CreateUserCommand,
GetUserCommand,
ListAccessKeysCommand,
ListAttachedUserPoliciesCommand,
ListPoliciesCommand,
NoSuchEntityException,
} = require('@aws-sdk/client-iam');
const { version } = require('../package.json');

const systemPrefix = '/scality-internal/';
Expand All @@ -26,8 +36,10 @@ function generateUserPolicyDocument() {
}

function createIAMClient(opts) {
return new IAM({
return new IAMClient({
endpoint: opts.iamEndpoint,
// Any region goes against Vault, but the client needs one to sign the request
region: process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1',
});
}

Expand Down Expand Up @@ -78,19 +90,17 @@ class UserHandler extends BaseHandler {
}

collect() {
return this.iamClient.getUser({
return this.iamClient.send(new GetUserCommand({
UserName: this.serviceName,
})
.promise()
}))
.then(res => res.User);
}

create(allResources) {
return this.iamClient.createUser({
return this.iamClient.send(new CreateUserCommand({
UserName: this.serviceName,
Path: systemPrefix,
})
.promise()
}))
.then(res => res.User);
}

Expand All @@ -105,24 +115,22 @@ class PolicyHandler extends BaseHandler {
}

collect() {
return this.iamClient.listPolicies({
return this.iamClient.send(new ListPoliciesCommand({
MaxItems: 100,
OnlyAttached: false,
Scope: 'All',
})
.promise()
.then(res => res.Policies.find(p => p.PolicyName === this.serviceName));
}))
.then(res => (res.Policies || []).find(p => p.PolicyName === this.serviceName));
}

create(allResources) {
const doc = generateUserPolicyDocument();

return this.iamClient.createPolicy({
return this.iamClient.send(new CreatePolicyCommand({
PolicyName: this.serviceName,
PolicyDocument: JSON.stringify(doc),
Path: systemPrefix,
})
.promise()
}))
.then(res => res.Policy);
}

Expand All @@ -137,20 +145,18 @@ class PolicyAttachmentHandler extends BaseHandler {
}

collect() {
return this.iamClient.listAttachedUserPolicies({
return this.iamClient.send(new ListAttachedUserPoliciesCommand({
UserName: this.serviceName,
MaxItems: 100,
})
.promise()
}))
.then(res => res.AttachedPolicies)
}

create(allResources) {
return this.iamClient.attachUserPolicy({
return this.iamClient.send(new AttachUserPolicyCommand({
PolicyArn: allResources.policy.Arn,
UserName: this.serviceName,
})
.promise();
}));
}

conflicts(p) {
Expand All @@ -164,19 +170,17 @@ class AccessKeyHandler extends BaseHandler {
}

collect() {
return this.iamClient.listAccessKeys({
return this.iamClient.send(new ListAccessKeysCommand({
UserName: this.serviceName,
MaxItems: 100,
})
.promise()
}))
.then(res => res.AccessKeyMetadata)
}

create(allResources) {
return this.iamClient.createAccessKey({
return this.iamClient.send(new CreateAccessKeyCommand({
UserName: this.serviceName,
})
.promise()
}))
.then(res => res.AccessKey);
}

Expand All @@ -189,7 +193,9 @@ function collectResource(v, done) {
v.collect()
.then(res => done(null, res))
.catch(err => {
if (err.code === 'NoSuchEntity') {
// Vault's NoSuchEntity deserializes to this exception, whose name is
// NoSuchEntityException: comparing against the wire code silently misses it
if (err instanceof NoSuchEntityException) {
return done(null, null);
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
},
"homepage": "https://github.com/scality/utapi#readme",
"dependencies": {
"@aws-sdk/client-iam": "^3.975.0",
"@hapi/joi": "^17.1.1",
"@senx/warp10": "1.0.14",
"arsenal": "git+https://github.com/scality/Arsenal#8.5.6",
"async": "^3.2.6",
"aws-sdk": "^2.1005.0",
"aws4": "^1.13.2",
"backo": "^1.1.0",
"body-parser": "^1.20.3",
Expand Down
3 changes: 2 additions & 1 deletion tests/functional/v2/testEnsureServiceUser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const assert = require('assert');
const { DeletePolicyCommand } = require('@aws-sdk/client-iam');

const vaultclient = require('../../utils/vaultclient');

Expand Down Expand Up @@ -43,7 +44,7 @@ describe('test bin/ensureServiceUser', () => {
const detached = await vaultclient.detachUserPolicies(adminAccount, { name: 'service-utapi-user' });
assert.strictEqual(detached.length, 1);
const client = vaultclient.getIAMClient(adminAccount);
await Promise.all(detached.map(PolicyArn => client.deletePolicy({ PolicyArn }).promise()));
await Promise.all(detached.map(PolicyArn => client.send(new DeletePolicyCommand({ PolicyArn }))));
await vaultclient.ensureServiceUser(adminAccount);

const res = await vaultclient.getInternalServiceUserAndPolicies(adminAccount);
Expand Down
63 changes: 36 additions & 27 deletions tests/utils/vaultclient.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
/* eslint-disable no-console */
const { IAM } = require('aws-sdk');
const {
IAMClient,
AttachUserPolicyCommand,
CreateAccessKeyCommand,
CreatePolicyCommand,
CreateUserCommand,
DeletePolicyCommand,
DeleteUserCommand,
DetachUserPolicyCommand,
GetPolicyVersionCommand,
GetUserCommand,
ListAttachedUserPoliciesCommand,
ListUsersCommand,
} = require('@aws-sdk/client-iam');
const vaultclient = require('vaultclient');
const fs = require('fs');
const { v4: uuid } = require('uuid');
Expand Down Expand Up @@ -107,18 +120,15 @@ class VaultClient {
}

static getIAMClient(credentials) {
const endpoint = process.env.VAULT_ENDPOINT || 'http://127.0.0.1:8600';
const info = {
endpoint,
sslEnabled: false,
return new IAMClient({
endpoint: process.env.VAULT_ENDPOINT || 'http://127.0.0.1:8600',
region: 'us-east-1',
apiVersion: '2010-05-08',
signatureVersion: 'v4',
accessKeyId: credentials.accessKey,
secretAccessKey: credentials.secretKey,
maxRetries: 0,
};
return new IAM(info);
credentials: {
accessKeyId: credentials.accessKey,
secretAccessKey: credentials.secretKey,
},
maxAttempts: 1,
});
}

static async createAccount(name) {
Expand Down Expand Up @@ -164,7 +174,7 @@ class VaultClient {

static async createUser(parentAccount, name, path) {
const client = VaultClient.getIAMClient(parentAccount);
const { User: user } = await client.createUser({ UserName: name, Path: path }).promise();
const { User: user } = await client.send(new CreateUserCommand({ UserName: name, Path: path }));
return {
name,
id: user.UserId,
Expand All @@ -175,7 +185,7 @@ class VaultClient {

static async createUserKeys(parentAccount, name) {
const client = VaultClient.getIAMClient(parentAccount);
const { AccessKey: creds } = await client.createAccessKey({ UserName: name }).promise();
const { AccessKey: creds } = await client.send(new CreateAccessKeyCommand({ UserName: name }));
return {
accessKey: creds.AccessKeyId,
secretKey: creds.SecretAccessKey,
Expand Down Expand Up @@ -209,9 +219,9 @@ class VaultClient {
const client = VaultClient.getIAMClient(parentAccount);
const PolicyDocument = VaultClient.templateUtapiPolicy(level, resource);
const PolicyName = `utapi-test-policy-${uuid()}`;
const res = await client.createPolicy({ PolicyName, PolicyDocument }).promise();
const res = await client.send(new CreatePolicyCommand({ PolicyName, PolicyDocument }));
const { Arn: PolicyArn } = res.Policy;
await client.attachUserPolicy({ PolicyArn, UserName: user.name }).promise();
await client.send(new AttachUserPolicyCommand({ PolicyArn, UserName: user.name }));
}

static async createInternalServiceAccount() {
Expand Down Expand Up @@ -244,7 +254,7 @@ class VaultClient {

static async getUserByName(parentAccount, name) {
const client = VaultClient.getIAMClient(parentAccount);
const { User: user } = await client.getUser({ UserName: name }).promise();
const { User: user } = await client.send(new GetUserCommand({ UserName: name }));
return {
name,
id: user.UserId,
Expand All @@ -255,12 +265,11 @@ class VaultClient {

static async getAttachedPolicies(parentAccount, user) {
const client = VaultClient.getIAMClient(parentAccount);
const res = await client.listAttachedUserPolicies({ UserName: user.name }).promise();
const { AttachedPolicies: attached } = res;
const res = await client.send(new ListAttachedUserPoliciesCommand({ UserName: user.name }));
const attached = res.AttachedPolicies || [];
const policies = await Promise.all(
attached.map(
({ PolicyArn }) => client.getPolicyVersion({ PolicyArn, VersionId: 'v1' })
.promise()
({ PolicyArn }) => client.send(new GetPolicyVersionCommand({ PolicyArn, VersionId: 'v1' }))
.then(({ PolicyVersion }) => ({
arn: PolicyArn,
document: JSON.parse(decodeURIComponent(PolicyVersion.Document)),
Expand All @@ -281,9 +290,9 @@ class VaultClient {

static async getAccountUsers(parentAccount) {
const client = VaultClient.getIAMClient(parentAccount);
const { Users } = await client.listUsers({}).promise();
const { Users } = await client.send(new ListUsersCommand({}));

return Users.map(user => ({
return (Users || []).map(user => ({
arn: user.Arn,
id: user.UserId,
name: user.UserName,
Expand All @@ -294,10 +303,10 @@ class VaultClient {
const client = VaultClient.getIAMClient(parentAccount);
const policies = await VaultClient.getAttachedPolicies(parentAccount, user);
return Promise.all(
policies.map(policy => client.detachUserPolicy({
policies.map(policy => client.send(new DetachUserPolicyCommand({
PolicyArn: policy.arn,
UserName: user.name,
}).promise().then(() => policy.arn)),
})).then(() => policy.arn)),
);
}

Expand All @@ -324,8 +333,8 @@ class VaultClient {
await Promise.all(
users.map(async user => {
const detached = await VaultClient.detachUserPolicies(parentAccount, user);
await Promise.all(detached.map(PolicyArn => client.deletePolicy({ PolicyArn }).promise()));
await client.deleteUser({ UserName: user.name }).promise();
await Promise.all(detached.map(PolicyArn => client.send(new DeletePolicyCommand({ PolicyArn }))));
await client.send(new DeleteUserCommand({ UserName: user.name }));
}),
);
}
Expand Down
Loading
Loading