-
Notifications
You must be signed in to change notification settings - Fork 4k
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
WIP: feat(ec2): add instance resource #3697
Conversation
Pull Request Checklist
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thorsten I think this is great addition.
readonly allowClassicSSH?: boolean; | ||
} | ||
|
||
export class BastionHost extends Instance { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if the class name should reflect the operating system, e.g BastionHostLinux .
Sadly a number of us have to use Windows Bastion Hosts
That would then leave scope for someone to create a Windows Version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sound great. I will change it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't Windows/Linux just be a setting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this to be a subclass of Instance
? What value do we get from that? Why not encapsulate?
this.addUserData('yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm'); | ||
|
||
if (props.allowClassicSSH) { | ||
this.connections.allowFromAnyIpv4(Port.tcp(22), 'Allow SSH access from everywhere'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this may be a bad idea; the end-user would be opening this instance up to the whole world by default.
The AWS Well-Architected Framework Security Pillar advises against,
There is also a rule in the CIS AWS Foundation to detect such security groups. ( Can be found in SecurityHub)
4.1 Ensure no security groups allow ingress from 0.0.0.0/0 to port 22
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fully agree with this objection. On the other hand, this is exactly what I see with many AWS users. If the server is up to date and key management is working correctly, it should not be a problem to have 22 open and it is the only way if your clients have dynamic IPs from their ISPs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about requiring the user to explicitly enable this via a setting which will be false
by default?
constructor(scope: Construct, id: string, props: InstanceProps) { | ||
super(scope, id); | ||
|
||
this.securityGroup = new SecurityGroup(this, 'InstanceSecurityGroup', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating a security group here, this could be passed on props, then only create a default group is one is not supplied.
This gives the user ultimate flexibility.
In an enterprise or regulated space, the SecuityGroup generally would have been created upfront by another team.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea because I do not like the SSH TCP access to the bastion server at all. So I will make the group suppliable and by default block any access. For the second part of your comment: I generally do not like the approach of other teams creating IAM roles or sec groups. The whole infrastructure should be defined and created by one team. If you want/have to have four-eye validation here I use the way of merge requests with approvals of the CISO/CSIRT or SCPs to prevent creating bad stuff. In an enterprise context, I would expect NACLs to be in place to prevent users from opening ports they are not allowed to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point agree, but I work in environments that these rules apply, compliance forbids single team.
I have just created PR for NACLs as they were missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the compliance issue with single teams, but the question is if you can solve this on an org level instead of a code level. So by using four-eyes merges instead of separated AWS roles. But this is OT of this PR. Change is on the way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am with @slipdexic - there are many users who simply are not allowed to create security groups or IAM roles and it makes sense to allow people to pass those in optionally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job!
/** | ||
* In which AZ to place the instance within the VPC | ||
* | ||
* @default - Random zone. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random bit stable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the order of props.vpc.selectSubnets()
stable? I use the first one.
/** | ||
* Use public subnet instead of private one | ||
* | ||
* @default - false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we use the standard SubnetSelection
type here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I map this to a standard SubnetSelection
so yes I could change it. I wanted to make the interface easier for users instead of handling subnet selections. Just tell me if you want it public or not. But I can just change it.
* | ||
* @default - false | ||
*/ | ||
readonly allowClassicSSH?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use enum with Ssh/Ssm instead of a boolean. Acronyms must be pascal cased (Ssh
instead of SSH
). Otherwise jsii can't delimit words.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed it regarding the other review comment
readonly allowClassicSSH?: boolean; | ||
} | ||
|
||
export class BastionHost extends Instance { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't Windows/Linux just be a setting?
this.addUserData('yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm'); | ||
|
||
if (props.allowClassicSSH) { | ||
this.connections.allowFromAnyIpv4(Port.tcp(22), 'Allow SSH access from everywhere'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about requiring the user to explicitly enable this via a setting which will be false
by default?
this.connections.allowFromAnyIpv4(Port.tcp(22), 'Allow SSH access from everywhere'); | ||
} | ||
|
||
new CfnOutput(this, 'BastionHostId', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be both for SSH and SSM? (description talks about SSM)
* assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com') | ||
* }); | ||
* | ||
* @default A role will automatically be created, it can be accessed via the `role` property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@default value should be -
constructor(scope: Construct, id: string, props: InstanceProps) { | ||
super(scope, id); | ||
|
||
this.securityGroup = new SecurityGroup(this, 'InstanceSecurityGroup', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am with @slipdexic - there are many users who simply are not allowed to create security groups or IAM roles and it makes sense to allow people to pass those in optionally.
}); | ||
this.connections = new Connections({ securityGroups: [this.securityGroup] }); | ||
this.securityGroups.push(this.securityGroup); | ||
this.node.applyAspect(new Tag(NAME_TAG, props.instanceName || this.node.path)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Tag.add
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied this from AutoScalingGroup but I changed it in this PR now.
@elad I subclassed |
Great work! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this!
Love the Instance; I wonder if we could make the bastion a little more clear and potentially flexible. What do you think?
* | ||
* @default - false | ||
*/ | ||
readonly publicSubnets?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be a subnetSelection
?
And shouldn't public subnets be the default? What use is a Bastion host in a private subnet?
EDIT: Oh I see, it probably has to do with whether you intend to access the bastion directly using SSH or using Session Manager. Could you at least make that clear in the documentation text? Or better yet, maybe it should be a property to select between the two?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are three scenarios:
- Public access using SSH -> public subnet
- Access via SSM -> private subnet
- Access using SSH from on-prem -> private subnet via VPN/DC
So public subnets should not be used imho...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re use cases, we use all of these and also have use case where we have had to have a baston host, (jump box) in all 3 zones.
When conducting penetration tests we will place a (bastion) host in each zone (public, private, ..), sometime the only way to test a zone get to jump through bastions in each zone using proxy chains.
So in my imho this construct should be able to live in any zone, just default to public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also you should be ideally able to place this in an explicit subnet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, so I still feel we should expose the subnetSelection
(and have it default to something sane, and I think PRIVATE
is the best sane default we can have). It would immediately benefit from future extensions we would add to the subnetSelection
mechanism (for example, selecting individual subnets such as @slipdexic requested). And it should have explicit documentation on there saying you need to select public subnets if you want to access it from the internet.
Would it make sense to have a top-level helper method for a common use case, bastion.allowSshAccessFrom(peer, peer, ...)
or something? The only thing it would do is encapsulate port 22, but it might be just a little nicer.
Also, @hoegertn, could you add a section to the README about the 2 Bastion Host use cases (SSM access and SSH access)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I will change it accordingly.
readonly availabilityZone?: string; | ||
|
||
/** | ||
* Whether the instance could initiate connections to anywhere by default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only used if securityGroup
is not supplied.
Codebuild (Continuous Integration) build failed for current commits. Please check log and resolve before PR is merged. |
Codebuild (Continuous Integration) build failed for current commits. Please check log and resolve before PR is merged. |
* | ||
* @experimental | ||
*/ | ||
export class BastionHostLinux extends Instance { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not seeing this before: I also agree with Elad that this host should probably encapsulate rather than extend Instance
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on why? I still think that a bastion host is a special instance and should derive from it, so users have all the options they have with an instance but with some defaults regarding the use case. I thought OOP is one of the benefits of CDK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We achieve OOP/polymorphism by implementing interfaces. Interfaces declare "a (this) can do the same thing as a (that)", regardless of the implementation. So if you want a Bastion Host to be-an Instance, then declare it like so:
class BastionHostLinux implements IInstance
Inheriting from a class brings both the "(this) can do the same as (that)" relationship, but it also couples one implementation to the other, and can have unintended implications for the future extensibility of the classes involved (either a change in the implementation of Instance
has an unintended side effect in the implementation of BastionHost
, or we now face the limitation that BastionHost
can only extend one class).
In some situations implementation sharing is a perfectly valid choice (which would usually be made to make it quicker to implement a certain class since you don't have to type so much), but polymorphism through interface implementation is generally a safer choice in the long term, when it's hard to predict future changes.
In this case, I'd rather be safe than sorry later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will change it and see what methods and fields I want to expose from the instance class.
Add
Instance
resource and create bastion host construct based on it.fixes #3174 and #1713
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license