Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: AppStream code hardening #583

Merged
merged 9 commits into from
Jul 20, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
"license": "Apache-2.0",
"dependencies": {
"@aws-ee/base-controllers": "workspace:*",
"@aws-ee/base-raas-services": "workspace:*",
"@aws-ee/base-raas-appstream-services": "workspace:*",
"@aws-ee/base-raas-services": "workspace:*",
"@aws-ee/base-services": "workspace:*",
"lodash": "^4.17.15"
"lodash": "^4.17.21"
},
"devDependencies": {
"eslint": "^6.8.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,7 @@ class AppStreamScService extends Service {
});

if (!stackName) {
throw this.boom.badRequest(
`Expected stack ${stackName} to be associated with the account ${accountId} but found`,
true,
);
throw this.boom.badRequest(`No AppStream stack is associated with the account ${accountId}`, true);
}

// Verify fleet is associated to appstream stack
Expand All @@ -90,7 +87,7 @@ class AppStreamScService extends Service {

if (!_.includes(fleetNames, fleetName)) {
throw this.boom.badRequest(
`Expected fleet ${fleetName} to be associated with the AppStream stack but found`,
`AppStream Fleet ${fleetName} is not associated with the AppStream stack ${stackName}`,
true,
);
}
Expand Down Expand Up @@ -130,14 +127,20 @@ class AppStreamScService extends Service {
indexId: environment.indexId,
});

const result = await appStream
.createStreamingURL({
FleetName: fleetName,
StackName: stackName,
UserId: this.generateUserId(requestContext, environment),
ApplicationId: applicationId,
})
.promise();
let result = {};

try {
result = await appStream
.createStreamingURL({
FleetName: fleetName,
StackName: stackName,
UserId: this.generateUserId(requestContext, environment),
ApplicationId: applicationId,
})
.promise();
} catch (err) {
throw this.boom.badRequest('There was an error generating AppStream URL', true);
}

// Write audit event
await this.audit(requestContext, { action: 'appstream-firefox-app-url-requested', body: { environmentId } });
Expand Down Expand Up @@ -173,15 +176,21 @@ class AppStreamScService extends Service {

const userId = this.generateUserId(requestContext, environment);
this.log.info({ msg: `Creating AppStream URL`, appStreamSessionUid: userId });
const result = await appStream
.createStreamingURL({
FleetName: fleetName,
StackName: stackName,
UserId: userId,
ApplicationId: 'MicrosoftRemoteDesktop',
SessionContext: privateIp,
})
.promise();

let result = {};
try {
result = await appStream
.createStreamingURL({
FleetName: fleetName,
StackName: stackName,
UserId: userId,
ApplicationId: 'MicrosoftRemoteDesktop',
SessionContext: privateIp,
})
.promise();
} catch (err) {
throw this.boom.badRequest('There was an error generating AppStream URL', true);
}

// Write audit event
await this.audit(requestContext, { action: 'appstream-remote-desktop-app-url-requested', body: { environmentId } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ const AppStreamScService = require('../appstream-sc-service');
describe('AppStreamScService', () => {
let service = null;
let environmentScService = null;
let indexesService = null;
let awsAccountsService = null;
let settings = null;

beforeAll(async () => {
const container = new ServicesContainer();
Expand All @@ -64,19 +67,185 @@ describe('AppStreamScService', () => {
// Get instance of the service we are testing
service = await container.find('appStreamScService');
environmentScService = await container.find('environmentScService');
awsAccountsService = await container.find('awsAccountsService');
indexesService = await container.find('indexesService');
settings = await container.find('settings');
});

beforeEach(async () => {
const aws = await service.service('aws');
AWSMock.setSDKInstance(aws.sdk);
});

afterEach(() => {
afterEach(async () => {
AWSMock.restore();
});

describe('urlForRemoteDesktop', () => {
it('should return AppStream url for environment', async () => {
describe('appstreamScService functions', () => {
it('should return empty body when sharing image with account', async () => {
// BUILD
const requestContext = { principalIdentifier: { uid: 'u-testuser' } };
const accountId = '999999999999';
const appStreamMock = {
updateImagePermissions: jest.fn(() => {
return {
promise: () => {
return {};
},
};
}),
};
service.getAppStream = jest.fn(() => {
return appStreamMock;
});
settings.get = jest.fn(input => input);

// OPERATE
const retVal = await service.shareAppStreamImageWithAccount(requestContext, accountId);

// ASSERT
expect(retVal).toEqual({});
expect(appStreamMock.updateImagePermissions).toHaveBeenCalledTimes(1);
expect(appStreamMock.updateImagePermissions).toHaveBeenCalledWith({
ImagePermissions: {
allowFleet: true,
allowImageBuilder: false,
},
Name: 'appStreamImageName',
SharedAccountId: accountId,
});
});

it('should return AppStream stack and fleet names', async () => {
// BUILD
const params = {
environmentId: 'exampleEnvId',
indexId: 'exampleIndexId',
};
const requestContext = { principalIdentifier: { uid: 'u-testuser' } };
const appStreamMock = {
listAssociatedFleets: jest.fn(() => {
return {
promise: () => {
return {
Names: ['exampleFleetName'],
};
},
};
}),
};
indexesService.mustFind = jest.fn(() => {
return { awsAccountId: 'abcd-1234-example-account-id' };
});
awsAccountsService.mustFind = jest.fn(() => {
return {
appStreamStackName: 'exampleStackName',
accountId: '999999999999',
appStreamFleetName: 'exampleFleetName',
};
});
environmentScService.getClientSdkWithEnvMgmtRole = jest.fn(() => {
return appStreamMock;
});

// OPERATE
const retVal = await service.getStackAndFleet(requestContext, params);

// ASSERT
expect(retVal).toEqual({ stackName: 'exampleStackName', fleetName: 'exampleFleetName' });
expect(indexesService.mustFind).toHaveBeenCalledWith(requestContext, { id: 'exampleIndexId' });
expect(awsAccountsService.mustFind).toHaveBeenCalledWith(requestContext, { id: 'abcd-1234-example-account-id' });
expect(appStreamMock.listAssociatedFleets).toHaveBeenCalledTimes(1);
expect(appStreamMock.listAssociatedFleets).toHaveBeenCalledWith({
StackName: 'exampleStackName',
});
});

it('should throw error when AppStream stack not associated', async () => {
// BUILD
const params = {
environmentId: 'exampleEnvId',
indexId: 'exampleIndexId',
};
const requestContext = { principalIdentifier: { uid: 'u-testuser' } };
const appStreamMock = {
listAssociatedFleets: jest.fn(() => {
return {
promise: () => {
return {
Names: ['exampleFleetName'],
};
},
};
}),
};
indexesService.mustFind = jest.fn(() => {
return { awsAccountId: 'abcd-1234-example-account-id' };
});
awsAccountsService.mustFind = jest.fn(() => {
return {
// No appStreamStackName returned back
accountId: '999999999999',
appStreamFleetName: 'exampleFleetName',
};
});
environmentScService.getClientSdkWithEnvMgmtRole = jest.fn(() => {
return appStreamMock;
});

try {
// OPERATE
await service.getStackAndFleet(requestContext, params);
} catch (err) {
// CHECK
expect(err.message).toEqual('No AppStream stack is associated with the account 999999999999');
}
});

it('should throw error when AppStream fleet not associated', async () => {
// BUILD
const params = {
environmentId: 'exampleEnvId',
indexId: 'exampleIndexId',
};
const requestContext = { principalIdentifier: { uid: 'u-testuser' } };
const appStreamMock = {
listAssociatedFleets: jest.fn(() => {
return {
promise: () => {
return {
Names: ['NotTheSameFleet'],
};
},
};
}),
};
indexesService.mustFind = jest.fn(() => {
return { awsAccountId: 'abcd-1234-example-account-id' };
});
awsAccountsService.mustFind = jest.fn(() => {
return {
appStreamStackName: 'exampleStackName',
accountId: '999999999999',
appStreamFleetName: 'exampleFleetName',
};
});
environmentScService.getClientSdkWithEnvMgmtRole = jest.fn(() => {
return appStreamMock;
});

try {
// OPERATE
await service.getStackAndFleet(requestContext, params);
} catch (err) {
// CHECK
expect(err.message).toEqual(
'AppStream Fleet exampleFleetName is not associated with the AppStream stack exampleStackName',
);
}
});

it('should return AppStream url for Windows environments', async () => {
// BUILD
const params = {
environmentId: 'env1',
Expand Down Expand Up @@ -153,5 +322,52 @@ describe('AppStreamScService', () => {
SessionContext: '10.0.78.193',
});
});

it('should return AppStream url for SageMaker and Linux environments', async () => {
// BUILD
const params = {
environmentId: 'env1',
applicationId: 'dummyApplication',
};
const requestContext = { principalIdentifier: { uid: 'u-testuser' } };
const appStreamMock = {
createStreamingURL: jest.fn(() => {
return {
promise: () => {
return {
StreamingURL: 'testurl',
};
},
};
}),
};
environmentScService.mustFind = jest.fn(() => {
return { indexId: 'exampleIndexId', createdAt: '2021-07-14T03:33:21.234Z' };
});
service.getStackAndFleet = jest.fn(() => {
return { stackName: 'testStack', fleetName: 'testFleet' };
});
environmentScService.getClientSdkWithEnvMgmtRole = jest.fn(() => {
return appStreamMock;
});

// OPERATE
const returnedUrl = await service.getStreamingUrl(requestContext, params);

// ASSERT
expect(returnedUrl).toEqual('testurl');
expect(environmentScService.mustFind).toHaveBeenCalledWith(requestContext, { id: 'env1' });
expect(service.getStackAndFleet).toHaveBeenCalledWith(requestContext, {
environmentId: 'env1',
indexId: 'exampleIndexId',
});
expect(appStreamMock.createStreamingURL).toHaveBeenCalledTimes(1);
expect(appStreamMock.createStreamingURL).toHaveBeenCalledWith({
FleetName: 'testFleet',
StackName: 'testStack',
UserId: 'u-testuser-2xhxhu',
ApplicationId: 'dummyApplication',
});
});
});
});
Loading