-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Adding granular SSH port forwarding controls #49417
Conversation
b1d783d
to
0d7d509
Compare
// port forwarding is allowed by default, we want to track explicit denies | ||
allowRemote := false | ||
allowLocal := false | ||
|
||
for _, role := range set { |
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.
suggestion:
- only use the legacy behavior if no role in the entire roleset uses the new options
- if using the new behavior, a single explicit legacy deny is a total deny
- a single explicit local or remote deny in the new option should result in that local/remote type being denied
something like this:
var (
legacyDeny bool
legacyAllow
denyLocal
denyRemote
usingNewOptions
)
for _, role := range set {
legacyDeny |= !types.BoolDefaultTrue(role.GetOptions().PortForwarding)
legacyAllow |= types.BoolDefaultTrue(role.GetOptions().PortForwarding)
config := role.GetOptions().SSHPortForwarding
if config == nil {
continue
}
usingNewOptions = true
if config.Remote != nil {
denyRemote |= !types.BoolDefaultTrue(config.Remote.Enabled)
}
if config.Local != nil {
denyLocal |= !types.BoolDefaultTrue(config.Local.Enabled)
}
}
if !usingNewOptions {
// Preserve legacy behavior, a single allow or unset option allows portforwarding
if legacyAllow {
return SSHPortForwardModeOn
}
return SSHPortForwardModeOff
}
switch {
case legacyDeny || denyLocal && denyRemote:
return SSHPortForwardModeOff
case denyLocal:
return SSHPortForwardModeRemote
case denyRemote:
return SSHPortForwardModeLocal
default:
return SSHPortForwardModeOn
}
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 use the legacy behavior if no role in the entire roleset uses the new options
- if using the new behavior, a single explicit legacy deny is a total deny
- a single explicit local or remote deny in the new option should result in that local/remote type being denied
That's actually what I started with and I agree that would make more sense if this were greenfield. Especially since I think that's more in line with how we handle similar cases. That's the opposite of how port_forwarding
works, though and it seems like a potential land mine if adding ssh_port_forwarding
to a single role effectively invalidates other roles that appear like they should allow forwarding.
I'm not necessarily against prioritizing explicit denies but the backwards compatibility concerns and potential breakage are things we'll want to weigh against
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.
That's the opposite of how port_forwarding works, though
Good, the way it currently works is terrible, I see this as an opportunity for us to rectify our past mistakes
it seems like a potential land mine if adding ssh_port_forwarding to a single role effectively invalidates other roles that appear like they should allow forwarding
imo it's a far bigger landmine if you can have an existing role with ssh_port_forwarding: {server: false}
, and then adding another role that doesn't mention port forwarding at all suddenly allows server port forwarding
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.
But yes we should definitely avoid breaking changes, that's why I think using the new field should be an opt-in to the new behavior, nothing will change until people start using this
Looks like you were discussing this with @rosstimothy, I'd be happy to chat about this with both of you, i feel fairly strongly we should take this opportunity to avoid the current state where roles that don't even mention port forwarding can completely override apparent "denies" in other roles
I am open to dropping my second bullet though "if using the new field, a single explicit legacy deny is a total deny", don't really care about this as long as behavior with roles that use the new field or nothing is sane
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.
@nklaassen what if we biased towards respecting the legacy behavior while preferring explicit denies in the new ssh_port_forwarding
config? I just pushed a commit that should represent what I mean exactly, but the most important points are:
port_forwarding
can be left undefined now rather than always defaulting to true.- If a role has an
ssh_port_forwarding
configured, theport_forwarding
field on the same role is ignored. - If a role has
port_forwarding: true
defined with nossh_port_forwarding
config, that's an immediate allow all which is what we would expect to happen today. - If no roles are using the legacy
port_forwarding
field,ssh_port_forwarding
biases towards explicit denies.
I think this maintains full backwards compatibility with existing behavior without forcing ssh_port_forwarding
to follow the same semantics. It also makes it possible for users to opt fully into the new model as long as they remove port_forwarding
from all of their existing roles.
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.
If a role has an ssh_port_forwarding configured, the port_forwarding field on the same role is ignored.
Can we make it an error to create or update a role with both set?
// port forwarding is allowed by default, we want to track explicit denies | ||
allowRemote := false | ||
allowLocal := false | ||
|
||
for _, role := range set { |
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 use the legacy behavior if no role in the entire roleset uses the new options
- if using the new behavior, a single explicit legacy deny is a total deny
- a single explicit local or remote deny in the new option should result in that local/remote type being denied
That's actually what I started with and I agree that would make more sense if this were greenfield. Especially since I think that's more in line with how we handle similar cases. That's the opposite of how port_forwarding
works, though and it seems like a potential land mine if adding ssh_port_forwarding
to a single role effectively invalidates other roles that appear like they should allow forwarding.
I'm not necessarily against prioritizing explicit denies but the backwards compatibility concerns and potential breakage are things we'll want to weigh against
From my perspective, there's two primary questions we need to answer:
For the second bullet, I think the possible options are:
I would also suggest we try to avoid changing the semantics of the legacy
All of these assume implicit allow when neither field is defined, but that's technically not possible right now because we enforce that Just to include my two cents on each option:
@nklaassen @rosstimothy Do you agree with these? Anything I missed that you think should be included? |
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 we also be updating the preset roles?
https://github.com/gravitational/teleport/blob/master/lib/services/presets.go#L120
https://github.com/gravitational/teleport/blob/master/lib/services/presets.go#L211
Can we extend regular.TestTCPIPForward, regular.TestDirectTCPIP, forward.TestCheckTCPIPForward, and forward.TestDirectTCPIP to ensure the SSH servers are doing the right thing as well? |
// port forwarding is allowed by default, we want to track explicit denies | ||
allowRemote := false | ||
allowLocal := false | ||
|
||
for _, role := range set { |
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.
If a role has an ssh_port_forwarding configured, the port_forwarding field on the same role is ignored.
Can we make it an error to create or update a role with both set?
lib/auth/auth.go
Outdated
PermitPortForwarding: req.checker.CanPortForward(), | ||
SSHPortForwardMode: req.checker.SSHPortForwardMode(), |
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.
let's say that my user has a single role with
ssh_port_forwarding:
remote: false
CanPortForward should return false here and the PermitPortForwarding extension in the cert should be unset. That's all good. I'm wondering if an older ssh node that doesn't know about the new role fields will respect that the extension is unset and block port forwarding, or if it ignores the cert and it will just allow port forwarding. Have you tested this out, or can you, and let us know here? Ideally I think we should try to prevent port forwarding on older nodes in that case
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.
As far as I can tell we don't reference the cert extension anywhere other than to set it. I'm doing another round of manual testing though so I'll double check that this works as expected 👍
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.
let us know how the testing goes, you're right I don't think we reference it anywhere, but there's a chance that golang.org/x/crypto/ssh
will honor it. But I think if you can port-forward to older nodes with my original example role we should try to find a mitigation (like downgrading the role for those nodes)
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 tested with a v16 agent and it allowed me to port forward even with ssh_port_forwarding
set to false across the board, which is what we expected to happen. I've got another PR ready to go behind this one that currently downgrades the role for older agents.
I actually think we can get away with always setting port_forwarding
appropriately rather than doing an optional downgrade based on version. Newer agents will ignore values for port_forwarding
in favor of ssh_port_forwarding
which should meanport_forwarding
is free to be leveraged as a way to signal correct behavior to older agents.
3346e85
to
0d932f2
Compare
1b1feae
to
661a0c2
Compare
f449270
to
e48a578
Compare
specifying within a role whether remote forwarding, local forwarding, both, or none should be allowed
e48a578
to
c4c712b
Compare
…49417) specifying within a role whether remote forwarding, local forwarding, both, or none should be allowed
This adds the implementation for the new
SSHPortForwarding
configuration added to the role proto here. It's meant to maintain existing default behaviors while allowing for more fine grained control over what types of SSH port forwarding a role should grant access to. The commits are broken up somewhat intentionally to make reviewing user.Expected cases can be seen more specifically in the test coverage itself, but at a high level the assumptions are:
ssh_port_forwarding
configuration is provided, theport_forwarding
bool on the same role is effectively ignored.RoleSet
takes precedence over everything else. This is different than current default behavior and there's a question about this below.port_forwarding
field that does not have a siblingssh_port_forwarding
configuration denies both local and remote forwarding.I'll be opening a follow up PR for maintaining backwards compatibility with older teleport agents that don't know about the new
ssh_port_forwarding
config.Questions
Current default behavior is that any occurrence ofport_forwarding: true
results in port forwarding being allowed. Do we want to maintain that going forward? It seems the opposite of what I would expect. So much so that I didn't even notice it at first.If we keep the default behavior above, should we adjust the default value ofport_forwarding
? In order to get granular controls to work, you would have to explicitly setport_forwarding: false
.port_forwarding
if there's an explicitssh_port_forwarding
field configured.changelog: Added more granular access controls for SSH port forwarding. Access to remote or local port forwarding can now be controlled individually using the new
ssh_port_forwarding
role option. TODO: include link to future docs