Merge pull request #15023 from hashicorp/ui/fix/token-secret-id-handling

ui: Fix token in cookie passthrough
This commit is contained in:
Michael Klein 2022-10-20 08:49:37 +02:00 committed by GitHub
commit ab31edaaa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 272 additions and 23 deletions

View File

@ -1,40 +1,19 @@
import Route from 'consul-ui/routing/route';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { runInDebug } from '@ember/debug';
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
export default class ApplicationRoute extends Route.extend(WithBlockingActions) {
@service('client/http') client;
@service('env') env;
@service('repository/token') tokenRepo;
@service('settings') settings;
@service() hcp;
data;
async model() {
if (this.env.var('CONSUL_ACLS_ENABLED')) {
const secret = this.env.var('CONSUL_HTTP_TOKEN');
const existing = await this.settings.findBySlug('token');
if (!existing.AccessorID && secret) {
try {
const token = await this.tokenRepo.self({
secret: secret,
dc: this.env.var('CONSUL_DATACENTER_LOCAL'),
});
await this.settings.persist({
token: {
AccessorID: token.AccessorID,
SecretID: token.SecretID,
Namespace: token.Namespace,
Partition: token.Partition,
},
});
} catch (e) {
runInDebug((_) => console.error(e));
}
}
await this.hcp.updateTokenIfNecessary(this.env.var('CONSUL_HTTP_TOKEN'));
}
return {};
}

View File

@ -12,3 +12,63 @@ export default class EnvService extends Service {
return env(key);
}
}
/**
* Stub class that can be used in testing when we want to test
* interactions with the EnvService. We can use `EnvStub.stubEnv` to setup
* an Env-Service that returns certain values we need to execute our tests.
*
* Example:
*
* ```js
* // some-test.js
* test('testing interaction with Env-service', async function(assert) {
* this.owner.register('service:env', class Stub extends EnvStub {
* . stubEnv = {
* CONSUL_ACLS_ENABLED: true
* }
* })
* })
* ```
*/
export class EnvStub extends EnvService {
var(key) {
const { stubEnv } = this;
const stubbed = stubEnv[key];
if (stubbed) {
return stubbed;
} else {
return super.var(...arguments);
}
}
}
/**
* Helper function to allow stubbing out data that is accessed by the application
* based on the Env-service. You will need to call this before the env-service gets
* initialized because it overrides the env-service injection on the owner.
*
* Example:
*
* ```js
* test('test something env related', async function(assert) {
* setupTestEnv(this.owner, {
* CONSUL_ACLS_ENABLED: true
* });
*
* // ...
* })
* ```
*
* @param {*} owner - the owner of the test instance (usually `this.owner`)
* @param {*} stubEnv - an object that holds the stubbed env-data
*/
export function setupTestEnv(owner, stubEnv) {
owner.register(
'service:env',
class Stub extends EnvStub {
stubEnv = stubEnv;
}
);
}

View File

@ -0,0 +1,37 @@
import Service, { inject as service } from '@ember/service';
import { runInDebug } from '@ember/debug';
/**
* A service to encapsulate all logic that handles dealing with setting up consul
* core correctly when started via HCP.
*/
export default class HCPService extends Service {
@service('env') env;
@service('repository/token') tokenRepo;
@service('settings') settings;
async updateTokenIfNecessary(secret) {
if (secret) {
const existing = await this.settings.findBySlug('token');
if (secret && secret !== existing.SecretID) {
try {
const token = await this.tokenRepo.self({
secret: secret,
dc: this.env.var('CONSUL_DATACENTER_LOCAL'),
});
await this.settings.persist({
token: {
AccessorID: token.AccessorID,
SecretID: token.SecretID,
Namespace: token.Namespace,
Partition: token.Partition,
},
});
} catch (e) {
runInDebug((_) => console.error(e));
}
}
}
}
}

View File

@ -0,0 +1,173 @@
import { module, test } from 'qunit';
import { visit } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { setupTestEnv } from 'consul-ui/services/env';
import TokenRepo from 'consul-ui/services/repository/token';
import SettingsService from 'consul-ui/services/settings';
const TOKEN_SET_BY_HCP = 'token-set-by-hcp';
module('Acceptance | hcp login', function (hooks) {
setupApplicationTest(hooks);
module('with `CONSUL_HTTP_TOKEN` not set', function () {
hooks.beforeEach(function () {
setupTestEnv(this.owner, {
CONSUL_ACLS_ENABLED: true,
});
});
test('we do not call the token endpoint', async function (assert) {
this.owner.register(
'service:repository/token',
class extends TokenRepo {
self() {
assert.step('token');
return super.self(...arguments);
}
}
);
await visit('/');
assert.verifySteps([], 'we do not try to fetch new token');
});
});
module('with `CONSUL_HTTP_TOKEN` set', function (hooks) {
hooks.beforeEach(function () {
setupTestEnv(this.owner, {
CONSUL_ACLS_ENABLED: true,
CONSUL_HTTP_TOKEN: TOKEN_SET_BY_HCP,
});
});
test('when no token was persisted to settings', async function (assert) {
assert.expect(3);
// stub out the settings service to not access local-storage directly
this.owner.register(
'service:settings',
class extends SettingsService {
async findBySlug(slug) {
// make sure we don't find anything
if (slug === 'token') {
// we return an empty string if nothing is found
return Promise.resolve('');
} else {
return super.findBySlug(...arguments);
}
}
}
);
// There's no way to hook into the api handlers like with mirage
// so we need to stub the repo methods
this.owner.register(
'service:repository/token',
class extends TokenRepo {
self(params) {
const { secret } = params;
assert.equal(
secret,
TOKEN_SET_BY_HCP,
'we try to request token based on what HCP set for us'
);
assert.step('token');
return super.self(...arguments);
}
}
);
await visit('/');
assert.verifySteps(['token'], 'we try to call token endpoint to fetch new token');
});
test('when we already persisted a token to settings and it is different to the secret HCP set for us', async function (assert) {
assert.expect(3);
this.owner.register(
'service:settings',
class extends SettingsService {
async findBySlug(slug) {
if (slug === 'token') {
return Promise.resolve({
AccessorID: 'accessor',
SecretID: 'secret',
Namespace: 'default',
Partition: 'default',
});
} else {
return super.findBySlug(...arguments);
}
}
}
);
this.owner.register(
'service:repository/token',
class extends TokenRepo {
self(params) {
const { secret } = params;
assert.equal(
secret,
TOKEN_SET_BY_HCP,
'we try to request token based on what HCP set for us'
);
assert.step('token');
return super.self(...arguments);
}
}
);
await visit('/');
assert.verifySteps(['token'], 'we call token endpoint to fetch new token');
});
test('when we already persisted a token to settings, but it is the same secret as HCP set for us', async function (assert) {
assert.expect(1);
this.owner.register(
'service:settings',
class extends SettingsService {
async findBySlug(slug) {
if (slug === 'token') {
return Promise.resolve({
AccessorID: 'accessor',
SecretID: TOKEN_SET_BY_HCP,
Namespace: 'default',
Partition: 'default',
});
} else {
return super.findBySlug(...arguments);
}
}
}
);
this.owner.register(
'service:repository/token',
class extends TokenRepo {
self() {
assert.step('token');
return super.self(...arguments);
}
}
);
await visit('/');
assert.verifySteps([], 'we do not try to fetch new token');
});
});
});