UNPKG

aws-cdk

Version:

CDK Toolkit, the command line tool for CDK apps

673 lines 100 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable import/no-extraneous-dependencies */ /** * THIS TEST SUITE DOES NOT USE aws-sdk-client-mock! * * We are asserting behavior of the STS Client used by SDKv3's credential provideres. * * In a recent SDKv3 change (between 3.699 and 3.730) the SDK team has bundled * the STS client into the credential providers. That means that the publicly * accessible (and hence mockable) STS Client is no longer the STS Client that * is used by the credential providers. * * We therefore cannot mock the STS Client, but instead we intercept network * calls and locally fake an STS Endpoint using the `FakeSts` class. */ const os = require("os"); const cdk_build_tools_1 = require("@aws-cdk/cdk-build-tools"); const cxapi = require("@aws-cdk/cx-api"); const fromEnv = require("@aws-sdk/credential-provider-env"); const promptly = require("promptly"); const uuid = require("uuid"); const fake_sts_1 = require("./fake-sts"); const aws_auth_1 = require("../../lib/api/aws-auth"); const awscli_compatible_1 = require("../../lib/api/aws-auth/awscli-compatible"); const user_agent_1 = require("../../lib/api/aws-auth/user-agent"); const plugin_1 = require("../../lib/api/plugin"); const mode_1 = require("../../lib/api/plugin/mode"); const cli_io_host_1 = require("../../lib/toolkit/cli-io-host"); const util_1 = require("../util"); const mock_sdk_1 = require("../util/mock-sdk"); // As part of the imports above we import `mock-sdk.ts` which automatically mocks // all SDK clients. We don't want that for this test suite, so undo it. (0, mock_sdk_1.undoAllSdkMocks)(); // Alloy mocking @aws-sdk/credential-provider-env jest.mock('@aws-sdk/credential-provider-env', () => ({ __esModule: true, ...jest.requireActual('@aws-sdk/credential-provider-env') })); let mockFetchMetadataToken = jest.fn(); let mockRequest = jest.fn(); jest.mock('@aws-sdk/ec2-metadata-service', () => { return { MetadataService: jest.fn().mockImplementation(() => { return { fetchMetadataToken: mockFetchMetadataToken, request: mockRequest, }; }), }; }); let uid; let pluginQueried; beforeEach(() => { // Cache busters! // We prefix everything with UUIDs because: // // - We have a cache from account# -> credentials // - We have a cache from access key -> account uid = `(${uuid.v4()})`; pluginQueried = false; cli_io_host_1.CliIoHost.instance().logLevel = 'trace'; plugin_1.PluginHost.instance.credentialProviderSources.splice(0); plugin_1.PluginHost.instance.credentialProviderSources.push({ isAvailable() { return Promise.resolve(true); }, canProvideCredentials(account) { return Promise.resolve(account === uniq('99999')); }, getProvider() { pluginQueried = true; return Promise.resolve({ accessKeyId: `${uid}plugin_key`, secretAccessKey: 'plugin_secret', sessionToken: 'plugin_token', }); }, name: 'test plugin', }); // Make sure these point to nonexistant files to start, if we don't call // prepare() then we don't accidentally want to fall back to system config. process.env.AWS_CONFIG_FILE = '/dev/null'; process.env.AWS_SHARED_CREDENTIALS_FILE = '/dev/null'; }); afterEach(() => { cli_io_host_1.CliIoHost.instance().logLevel = 'info'; cdk_build_tools_1.bockfs.restore(); jest.restoreAllMocks(); }); function uniq(account) { return `${uid}${account}`; } function env(account) { return cxapi.EnvironmentUtils.make(account, 'def'); } describe('with intercepted network calls', () => { // Most tests will use intercepted network calls, except one test that tests // that the right HTTP `Agent` is used. let fakeSts; beforeEach(() => { fakeSts = new fake_sts_1.FakeSts(); fakeSts.begin(); // Make sure the KeyID returned by the plugin is recognized fakeSts.registerUser(uniq('99999'), uniq('plugin_key')); mockRequest = jest.fn().mockResolvedValue(JSON.stringify({ region: undefined })); }); afterEach(() => { fakeSts.restore(); }); // Set of tests where the CDK will not trigger assume-role // (the INI file might still do assume-role) describe('when CDK does not AssumeRole', () => { test('uses default credentials by default', async () => { // WHEN const account = uniq('11111'); prepareCreds({ fakeSts, credentials: { default: { aws_access_key_id: 'access', $account: '11111', $fakeStsOptions: { partition: 'aws-here' } }, }, config: { default: { region: 'eu-bla-5' }, }, }); const provider = await providerFromProfile(undefined); // THEN expect(provider.defaultRegion).toEqual('eu-bla-5'); await expect(provider.defaultAccount()).resolves.toEqual({ accountId: account, partition: 'aws-here' }); // Ask for a different region const sdk = (await provider.forEnvironment({ ...env(account), region: 'rgn' }, mode_1.Mode.ForReading)).sdk; expect((await sdkConfig(sdk).credentials()).accessKeyId).toEqual(uniq('access')); expect(sdk.currentRegion).toEqual('rgn'); }); test('throws if no credentials could be found', async () => { const account = uniq('11111'); const provider = await providerFromProfile(undefined); await expect(exerciseCredentials(provider, { ...env(account), region: 'rgn' })) .rejects .toThrow(/Need to perform AWS calls for account .*, but no credentials have been configured, and none of these plugins found any/); }); test('no base credentials partition if token is expired', async () => { const account = uniq('11111'); const error = new Error('Expired Token'); error.name = 'ExpiredToken'; const identityProvider = () => Promise.reject(error); const provider = new aws_auth_1.SdkProvider(identityProvider, 'rgn'); const creds = await provider.baseCredentialsPartition({ ...env(account), region: 'rgn' }, mode_1.Mode.ForReading); expect(creds).toBeUndefined(); }); test('throws if profile credentials are not for the right account', async () => { // WHEN jest.spyOn(awscli_compatible_1.AwsCliCompatible, 'region').mockResolvedValue('us-east-123'); prepareCreds({ fakeSts, config: { 'profile boo': { aws_access_key_id: 'access', $account: '11111' }, }, }); const provider = await providerFromProfile('boo'); await expect(exerciseCredentials(provider, env(uniq('some_account_#')))).rejects.toThrow('Need to perform AWS calls'); }); test('use profile acct/region if agnostic env requested', async () => { // WHEN prepareCreds({ fakeSts, credentials: { default: { aws_access_key_id: 'access', $account: '11111' }, }, config: { default: { region: 'eu-bla-5' }, }, }); const provider = await providerFromProfile(undefined); // THEN const sdk = (await provider.forEnvironment(cxapi.EnvironmentUtils.make(cxapi.UNKNOWN_ACCOUNT, cxapi.UNKNOWN_REGION), mode_1.Mode.ForReading)).sdk; expect((await sdkConfig(sdk).credentials()).accessKeyId).toEqual(uniq('access')); expect((await sdk.currentAccount()).accountId).toEqual(uniq('11111')); expect(sdk.currentRegion).toEqual('eu-bla-5'); }); test('passing profile skips EnvironmentCredentials', async () => { // GIVEN const fe = jest.spyOn(fromEnv, 'fromEnv'); prepareCreds({ fakeSts, credentials: { foo: { aws_access_key_id: 'access', $account: '11111' }, }, }); const provider = await providerFromProfile('foo'); await provider.defaultAccount(); expect(fe).not.toHaveBeenCalled(); }); test('supports profile spread over config_file and credentials_file', async () => { // WHEN prepareCreds({ fakeSts, credentials: { foo: { aws_access_key_id: 'fooccess', $account: '22222' }, }, config: { 'default': { region: 'eu-bla-5' }, 'profile foo': { region: 'eu-west-1' }, }, }); const provider = await providerFromProfile('foo'); // THEN expect(provider.defaultRegion).toEqual('eu-west-1'); await expect(provider.defaultAccount()).resolves.toEqual({ accountId: uniq('22222'), partition: 'aws' }); const sdk = (await provider.forEnvironment(env(uniq('22222')), mode_1.Mode.ForReading)).sdk; expect((await sdkConfig(sdk).credentials()).accessKeyId).toEqual(uniq('fooccess')); }); test('supports profile only in config_file', async () => { // WHEN prepareCreds({ fakeSts, config: { 'default': { region: 'eu-bla-5' }, 'profile foo': { aws_access_key_id: 'fooccess', $account: '22222' }, }, }); const provider = await providerFromProfile('foo'); // THEN expect(provider.defaultRegion).toEqual('eu-bla-5'); // Fall back to default config await expect(provider.defaultAccount()).resolves.toEqual({ accountId: uniq('22222'), partition: 'aws' }); const sdk = (await provider.forEnvironment(env(uniq('22222')), mode_1.Mode.ForReading)).sdk; expect((await sdkConfig(sdk).credentials()).accessKeyId).toEqual(uniq('fooccess')); }); test('can assume-role configured in config', async () => { // GIVEN jest.spyOn(console, 'debug'); prepareCreds({ fakeSts, credentials: { assumer: { aws_access_key_id: 'assumer', $account: '11111' }, }, config: { 'default': { region: 'eu-bla-5' }, 'profile assumer': { region: 'us-east-2' }, 'profile assumable': { role_arn: 'arn:aws:iam::66666:role/Assumable', source_profile: 'assumer', $account: '66666', $fakeStsOptions: { allowedAccounts: ['11111'] }, }, }, }); const provider = await providerFromProfile('assumable'); // WHEN const sdk = (await provider.forEnvironment(env(uniq('66666')), mode_1.Mode.ForReading)).sdk; // THEN expect((await sdk.currentAccount()).accountId).toEqual(uniq('66666')); }); test('can assume role even if [default] profile is missing', async () => { // GIVEN prepareCreds({ fakeSts, credentials: { assumer: { aws_access_key_id: 'assumer', $account: '22222' }, assumable: { role_arn: 'arn:aws:iam::12356789012:role/Assumable', source_profile: 'assumer', $account: '22222', }, }, config: { 'profile assumable': { region: 'eu-bla-5' }, }, }); // WHEN const provider = await providerFromProfile('assumable'); // THEN expect((await provider.defaultAccount())?.accountId).toEqual(uniq('22222')); }); const providersForMfa = [ (() => providerFromProfile('mfa-role')), (async () => { // The profile is not passed explicitly. Should be picked from the environment variable process.env.AWS_PROFILE = 'mfa-role'; // Awaiting to make sure the environment variable is only deleted after it's used const provider = await aws_auth_1.SdkProvider.withAwsCliCompatibleDefaults({ logger: console }); delete process.env.AWS_PROFILE; return Promise.resolve(provider); }), ]; test.each(providersForMfa)('mfa_serial in profile will ask user for token', async (metaProvider) => { // GIVEN const mockPrompt = jest.spyOn(promptly, 'prompt').mockResolvedValue('1234'); prepareCreds({ fakeSts, credentials: { assumer: { aws_access_key_id: 'assumer', $account: '66666' }, }, config: { 'default': { region: 'eu-bla-5' }, 'profile assumer': { region: 'us-east-2' }, 'profile mfa-role': { role_arn: 'arn:aws:iam::66666:role/Assumable', source_profile: 'assumer', mfa_serial: 'arn:aws:iam::account:mfa/user', $account: '66666', }, }, }); const provider = await metaProvider(); // THEN const sdk = (await provider.forEnvironment(env(uniq('66666')), mode_1.Mode.ForReading)).sdk; expect((await sdk.currentAccount()).accountId).toEqual(uniq('66666')); // Make sure the MFA mock was called during this test, only once // (Credentials need to remain cached) expect(mockPrompt).toHaveBeenCalledTimes(1); }); }); // For DefaultSynthesis we will do an assume-role after having gotten base credentials describe('when CDK AssumeRoles', () => { beforeEach(() => { // All these tests share that 'arn:aws:role' is a role into account 88888 which can be assumed from 11111 fakeSts.registerRole(uniq('88888'), 'arn:aws:role', { allowedAccounts: [uniq('11111')] }); }); test('error we get from assuming a role is useful', async () => { // GIVEN prepareCreds({ fakeSts, config: { default: { aws_access_key_id: 'foo' }, }, }); const provider = await providerFromProfile(undefined); // WHEN const promise = exerciseCredentials(provider, env(uniq('88888')), mode_1.Mode.ForReading, { assumeRoleArn: 'doesnotexist.role.arn', }); // THEN - error message contains both a helpful hint and the underlying AssumeRole message await expect(promise).rejects.toThrow('(re)-bootstrap the environment'); await expect(promise).rejects.toThrow('doesnotexist.role.arn'); }); test('assuming a role sanitizes the username into the session name', async () => { // GIVEN prepareCreds({ fakeSts, config: { default: { aws_access_key_id: 'foo', $account: '11111' }, }, }); await (0, util_1.withMocked)(os, 'userInfo', async (userInfo) => { userInfo.mockReturnValue({ username: 'skål', uid: 1, gid: 1, homedir: '/here', shell: '/bin/sh' }); // WHEN const provider = await providerFromProfile(undefined); const sdk = (await provider.forEnvironment(env(uniq('88888')), mode_1.Mode.ForReading, { assumeRoleArn: 'arn:aws:role' })).sdk; await sdk.currentAccount(); // THEN expect(fakeSts.assumedRoles).toContainEqual(expect.objectContaining({ roleSessionName: 'aws-cdk-sk@l', })); }); }); test('session tags can be passed when assuming a role', async () => { // GIVEN prepareCreds({ fakeSts, config: { default: { aws_access_key_id: 'foo', $account: '11111' }, }, }); await (0, util_1.withMocked)(os, 'userInfo', async (userInfo) => { userInfo.mockReturnValue({ username: 'skål', uid: 1, gid: 1, homedir: '/here', shell: '/bin/sh' }); // WHEN const provider = await providerFromProfile(undefined); const sdk = (await provider.forEnvironment(env(uniq('88888')), mode_1.Mode.ForReading, { assumeRoleArn: 'arn:aws:role', assumeRoleExternalId: 'bruh', assumeRoleAdditionalOptions: { Tags: [{ Key: 'Department', Value: 'Engineering' }], }, })).sdk; await sdk.currentAccount(); // THEN expect(fakeSts.assumedRoles).toContainEqual(expect.objectContaining({ tags: [{ Key: 'Department', Value: 'Engineering' }], transitiveTagKeys: ['Department'], roleArn: 'arn:aws:role', externalId: 'bruh', roleSessionName: 'aws-cdk-sk@l', })); }); }); test('assuming a role does not fail when OS username cannot be read', async () => { // GIVEN prepareCreds({ fakeSts, config: { default: { aws_access_key_id: 'foo', $account: '11111' }, }, }); await (0, util_1.withMocked)(os, 'userInfo', async (userInfo) => { userInfo.mockImplementation(() => { // SystemError thrown as documented: https://nodejs.org/docs/latest-v16.x/api/os.html#osuserinfooptions throw new Error('SystemError on Linux: uv_os_get_passwd returned ENOENT. See #19401 issue.'); }); // WHEN const provider = await providerFromProfile(undefined); await exerciseCredentials(provider, env(uniq('88888')), mode_1.Mode.ForReading, { assumeRoleArn: 'arn:aws:role' }); // THEN expect(fakeSts.assumedRoles).toContainEqual(expect.objectContaining({ roleArn: 'arn:aws:role', roleSessionName: 'aws-cdk-noname', })); }); }); test('even if current credentials are for the wrong account, we will still use them to AssumeRole', async () => { // GIVEN prepareCreds({ fakeSts, config: { default: { aws_access_key_id: 'foo', $account: '11111' }, }, }); const provider = await providerFromProfile(undefined); // WHEN const sdk = (await provider.forEnvironment(env(uniq('88888')), mode_1.Mode.ForReading, { assumeRoleArn: 'arn:aws:role' })).sdk; // THEN expect((await sdk.currentAccount()).accountId).toEqual(uniq('88888')); }); test('if AssumeRole fails but current credentials are for the right account, we will still use them', async () => { // GIVEN prepareCreds({ fakeSts, config: { default: { aws_access_key_id: 'foo', $account: '88888' }, }, }); const provider = await providerFromProfile(undefined); // WHEN - assumeRole fails because the role can only be assumed from account 11111 const sdk = (await provider.forEnvironment(env(uniq('88888')), mode_1.Mode.ForReading, { assumeRoleArn: 'arn:aws:role' })).sdk; // THEN expect((await sdk.currentAccount()).accountId).toEqual(uniq('88888')); }); test('if AssumeRole fails because of ExpiredToken, then fail completely', async () => { // GIVEN prepareCreds({ fakeSts, config: { default: { aws_access_key_id: 'foo', $account: '88888' }, }, }); const provider = await providerFromProfile(undefined); // WHEN - assumeRole fails with a specific error await expect(exerciseCredentials(provider, env(uniq('88888')), mode_1.Mode.ForReading, { assumeRoleArn: '<FAIL:ExpiredToken>' })) .rejects.toThrow(/ExpiredToken/); }); }); describe('Plugins', () => { test('does not use plugins if current credentials are for expected account', async () => { prepareCreds({ fakeSts, config: { default: { aws_access_key_id: 'foo', $account: '11111' }, }, }); const provider = await providerFromProfile(undefined); await exerciseCredentials(provider, env(uniq('11111'))); expect(pluginQueried).toEqual(false); }); test('uses plugin for account 99999', async () => { const provider = await providerFromProfile(undefined); await exerciseCredentials(provider, env(uniq('99999'))); expect(pluginQueried).toEqual(true); }); test('can assume role with credentials from plugin', async () => { fakeSts.registerRole(uniq('99999'), 'arn:aws:iam::99999:role/Assumable'); const provider = await providerFromProfile(undefined); await exerciseCredentials(provider, env(uniq('99999')), mode_1.Mode.ForReading, { assumeRoleArn: 'arn:aws:iam::99999:role/Assumable', }); expect(pluginQueried).toEqual(true); expect(fakeSts.assumedRoles).toContainEqual(expect.objectContaining({ roleArn: 'arn:aws:iam::99999:role/Assumable', roleSessionName: expect.anything(), })); }); test('even if AssumeRole fails but current credentials are from a plugin, we will still use them', async () => { const provider = await providerFromProfile(undefined); const sdk = (await provider.forEnvironment(env(uniq('99999')), mode_1.Mode.ForReading, { assumeRoleArn: 'does:not:exist' })).sdk; // THEN expect((await sdk.currentAccount()).accountId).toEqual(uniq('99999')); }); test('plugins are still queried even if current credentials are expired (or otherwise invalid)', async () => { // GIVEN prepareCreds({ credentials: { default: { aws_access_key_id: `${uid}akid`, $account: '11111', $fakeStsOptions: { partition: 'aws-here' } }, }, config: { default: { region: 'eu-bla-5' }, }, }); process.env.AWS_ACCESS_KEY_ID = `${uid}akid`; process.env.AWS_SECRET_ACCESS_KEY = 'sekrit'; const provider = await providerFromProfile(undefined); // WHEN await exerciseCredentials(provider, env(uniq('99999'))); // THEN expect(pluginQueried).toEqual(true); }); }); describe('support for credential_source', () => { test('can assume role with ecs credentials', async () => { // GIVEN const calls = jest.spyOn(console, 'debug'); prepareCreds({ config: { 'profile ecs': { role_arn: 'arn:aws:iam::12356789012:role/Assumable', credential_source: 'EcsContainer', $account: '22222', }, }, }); // WHEN const provider = await providerFromProfile('ecs'); await provider.defaultAccount(); // THEN expect(calls.mock.calls).toContainEqual([ '@aws-sdk/credential-provider-ini - finding credential resolver using profile=[ecs]', ]); expect(calls.mock.calls).toContainEqual(['@aws-sdk/credential-provider-ini - credential_source is EcsContainer']); }); test('can assume role with ec2 credentials', async () => { // GIVEN const calls = jest.spyOn(console, 'debug'); prepareCreds({ config: { 'profile ecs': { role_arn: 'arn:aws:iam::12356789012:role/Assumable', credential_source: 'Ec2InstanceMetadata', $account: '22222', }, }, }); // WHEN const provider = await providerFromProfile('ecs'); await provider.defaultAccount(); // THEN expect(calls.mock.calls).toContainEqual([ '@aws-sdk/credential-provider-ini - finding credential resolver using profile=[ecs]', ]); expect(calls.mock.calls).toContainEqual([ '@aws-sdk/credential-provider-ini - credential_source is Ec2InstanceMetadata', ]); }); test('can assume role with env credentials', async () => { // GIVEN const calls = jest.spyOn(console, 'debug'); prepareCreds({ config: { 'profile ecs': { role_arn: 'arn:aws:iam::12356789012:role/Assumable', credential_source: 'Environment', $account: '22222', }, }, }); // WHEN const provider = await providerFromProfile('ecs'); await provider.defaultAccount(); // THEN expect(calls.mock.calls).toContainEqual([ '@aws-sdk/credential-provider-ini - finding credential resolver using profile=[ecs]', ]); expect(calls.mock.calls).toContainEqual(['@aws-sdk/credential-provider-ini - credential_source is Environment']); }); test('assume fails with unsupported credential_source', async () => { // GIVEN prepareCreds({ config: { 'profile ecs': { role_arn: 'arn:aws:iam::12356789012:role/Assumable', credential_source: 'unsupported', $account: '22222', }, }, }); const provider = await providerFromProfile('ecs'); // WHEN const account = await provider.defaultAccount(); // THEN expect(account?.accountId).toEqual(undefined); }); }); test('defaultAccount returns undefined if STS call fails', async () => { // GIVEN fakeSts.failAssumeRole = new Error('Oops, bad sekrit'); // WHEN const provider = await providerFromProfile(undefined); // THEN await expect(provider.defaultAccount()).resolves.toBe(undefined); }); test('defaultAccount returns undefined, event if STS call fails with ExpiredToken', async () => { // GIVEN const error = new Error('Too late'); error.name = 'ExpiredToken'; fakeSts.failAssumeRole = error; // WHEN const provider = await providerFromProfile(undefined); // THEN await expect(provider.defaultAccount()).resolves.toBe(undefined); }); }); test('default useragent is reasonable', () => { expect((0, user_agent_1.defaultCliUserAgent)()).toContain('aws-cdk/'); }); /** * Use object hackery to get the credentials out of the SDK object */ function sdkConfig(sdk) { return sdk.config; } /** * Fixture for SDK auth for this test suite * * Has knowledge of the cache buster, will write proper fake config files and * register users and roles in FakeSts at the same time. */ function prepareCreds(options) { function convertSections(sections) { const ret = []; for (const [profile, user] of Object.entries(sections ?? {})) { ret.push(`[${profile}]`); if (isProfileRole(user)) { ret.push(`role_arn=${user.role_arn}`); if ('source_profile' in user) { ret.push(`source_profile=${user.source_profile}`); } if ('credential_source' in user) { ret.push(`credential_source=${user.credential_source}`); } if (user.mfa_serial) { ret.push(`mfa_serial=${user.mfa_serial}`); } options.fakeSts?.registerRole(uniq(user.$account ?? '00000'), user.role_arn, { ...user.$fakeStsOptions, allowedAccounts: user.$fakeStsOptions?.allowedAccounts?.map(uniq), }); } else { if (user.aws_access_key_id) { ret.push(`aws_access_key_id=${uniq(user.aws_access_key_id)}`); ret.push('aws_secret_access_key=secret'); options.fakeSts?.registerUser(uniq(user.$account ?? '00000'), uniq(user.aws_access_key_id), user.$fakeStsOptions); } } if (user.region) { ret.push(`region=${user.region}`); } } return ret.join('\n'); } (0, cdk_build_tools_1.bockfs)({ '/home/me/.bxt/credentials': convertSections(options.credentials), '/home/me/.bxt/config': convertSections(options.config), }); // Set environment variables that we want process.env.AWS_CONFIG_FILE = cdk_build_tools_1.bockfs.path('/home/me/.bxt/config'); process.env.AWS_SHARED_CREDENTIALS_FILE = cdk_build_tools_1.bockfs.path('/home/me/.bxt/credentials'); } function isProfileRole(x) { return 'role_arn' in x; } async function providerFromProfile(profile) { return aws_auth_1.SdkProvider.withAwsCliCompatibleDefaults({ profile, logger: console }); } async function exerciseCredentials(provider, e, mode = mode_1.Mode.ForReading, options) { const sdk = await provider.forEnvironment(e, mode, options); await sdk.sdk.currentAccount(); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2RrLXByb3ZpZGVyLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJzZGstcHJvdmlkZXIudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHNEQUFzRDtBQUN0RDs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCx5QkFBeUI7QUFDekIsOERBQWtEO0FBQ2xELHlDQUF5QztBQUN6Qyw0REFBNEQ7QUFDNUQscUNBQXFDO0FBQ3JDLDZCQUE2QjtBQUM3Qix5Q0FBK0U7QUFDL0UscURBQW9HO0FBQ3BHLGdGQUE0RTtBQUM1RSxrRUFBd0U7QUFDeEUsaURBQWtEO0FBQ2xELG9EQUFpRDtBQUNqRCwrREFBMEQ7QUFDMUQsa0NBQXFDO0FBQ3JDLCtDQUFtRDtBQUVuRCxpRkFBaUY7QUFDakYsdUVBQXVFO0FBQ3ZFLElBQUEsMEJBQWUsR0FBRSxDQUFDO0FBRWxCLGlEQUFpRDtBQUNqRCxJQUFJLENBQUMsSUFBSSxDQUFDLGtDQUFrQyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxrQ0FBa0MsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBRXZJLElBQUksc0JBQXNCLEdBQUcsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDO0FBQ3ZDLElBQUksV0FBVyxHQUFHLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQztBQUU1QixJQUFJLENBQUMsSUFBSSxDQUFDLCtCQUErQixFQUFFLEdBQUcsRUFBRTtJQUM5QyxPQUFPO1FBQ0wsZUFBZSxFQUFFLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQyxrQkFBa0IsQ0FBQyxHQUFHLEVBQUU7WUFDakQsT0FBTztnQkFDTCxrQkFBa0IsRUFBRSxzQkFBc0I7Z0JBQzFDLE9BQU8sRUFBRSxXQUFXO2FBQ3JCLENBQUM7UUFDSixDQUFDLENBQUM7S0FDSCxDQUFDO0FBQ0osQ0FBQyxDQUFDLENBQUM7QUFFSCxJQUFJLEdBQVcsQ0FB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