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

Add Ports field to app spec #47706

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open

Add Ports field to app spec #47706

wants to merge 12 commits into from

Conversation

ravicious
Copy link
Member

@ravicious ravicious commented Oct 18, 2024

This PR makes it possible to configure multi-port TCP apps. This is in accordance with RFD 182. The goal is to be able to define an app with multiple ports like this:

kind: app
version: v3
metadata:
  name: multi-port-app
  labels:
    env: test
spec:
  uri: tcp://localhost
  ports:
    - port: 22
    - port: 443
    - port: 555
      end_port: 679

This PR adds Ports and relevant validation to both api/types and lib/config. The new field is validated no matter if someone attempts to add a dynamic app or include an app in an instance config.

This specific format was chosen after a brief discussion (#46169 (comment)) base on prior art of network policies in Kubernetes.

I'll wait with merging this PR until I have the backend implementation ready.

@ravicious ravicious added do-not-merge no-changelog Indicates that a PR does not require a changelog entry labels Oct 18, 2024
return nil
}

func (a *App) checkPorts() error {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the fact that this is just a copy of what's already in api/types/app.go. But I also noticed that we already duplicate a bunch of stuff between those two locations.

Should I instead create api/types.ValidateAppPorts and create an interface with GetPort and GetEndPort so that I can reuse a single function between api/types and lib/service/servicecfg?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move it to somewhere else and re-use it.
Eg api/utils/net.ValidatePortRange(port, portEnd int) error

uint32 Port = 1 [(gogoproto.jsontag) = "port"];
// EndPort describes the end of the range, inclusive. It must be between 2-65535 and be greater
// than Port when describing a port range. When describing a single port, it must be set to 0.
uint32 EndPort = 2 [(gogoproto.jsontag) = "end_port,omitempty"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe transforming this field into optional field?

I wonder if we shouldn't have

  • Port
  • StartPort
  • EndPort

We validate so you can't specify Port and any of StartPort/Endport as it reads far easier or transform this into a oneof Port vs Range.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe transforming this field into optional field?

How exactly? My impression was that this field is already optional, as when describing a single port you can omit it. I should change the comment though to spell that out loud explicitly.


Regarding StartPort, I don't think Port & EndPort reads that terribly to warrant using one of the other two options. With three fields in a single struct, there's more invalid states that can be represented. With a oneof, as far as I understand, we'd lose some simplicity in lib/config & lib/service/servicecfg mirroring the structures defined in api/types. oneof in protos necessitates a wrapper struct which we'd have to replicate in those two packages.

Or I guess it could be something like this:

type PortSpec struct {
  Port Port
  PortRange PortRange
} 

…or some variation of it. But then again we'd have more stuff to validate and those three packages would no longer mirror their data structures in this specific case. Which I'm not sure how worthy of a goal it is to strive for, but it seems to me like a trait worth having.

Copy link
Contributor

@tigrato tigrato Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly? My impression was that this field is already optional, as when describing a single port you can omit it. I should change the comment though to spell that out loud explicitly.

Proto3 recently introduced the optional keyword that transforms the field into *fieldType. It's just a simpler way to say it out loud that it's not mandatory.


My idea is to keep it as simple as possible and be able to evolve independently. I don't think it reads bad, I just wonder if we want to evolve them in the future separately but if we don't want to do it, that's fine.
A brief history, network policies already exist in Kubernetes for ~4y when they added support for ranges, so to keep the compatibility, they kept the port and introduced the endPort

api/proto/teleport/legacy/types/types.proto Outdated Show resolved Hide resolved
@ravicious
Copy link
Member Author

@espadolini @marcoandredinis Ping.

lib/service/servicecfg/config_test.go Outdated Show resolved Hide resolved
lib/config/fileconf.go Show resolved Hide resolved
return nil
}

func (a *App) checkPorts() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move it to somewhere else and re-use it.
Eg api/utils/net.ValidatePortRange(port, portEnd int) error

Copy link
Contributor

@espadolini espadolini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM after Marco's comments are addressed.

We should probably add a compatibility notice to the docs with regards to the behavior of multi-port TCP apps and older agents.

Copy link

github-actions bot commented Nov 7, 2024

🤖 Vercel preview here: https://docs-84qfg2ra5-goteleport.vercel.app/docs/ver/preview

// ValidatePortRange checks if the given port range is within bounds. If endPort is not zero, it
// also checks if it's bigger than port. A port range with zero as endPort is assumed to describe a
// single port.
func ValidatePortRange(port, endPort int) error {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcoandredinis Could you take a look at c931742 which extracts port range validation to api/utils/net.ValidatePortRange? I want to be sure that whatever I'm pushing to api looks perfect.

Copy link
Contributor

@marcoandredinis marcoandredinis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just simplify the endPort validation, but up to you 👍

Comment on lines +32 to +40
if endPort != 0 {
if endPort < minPort+1 || endPort > maxPort {
return trace.BadParameter("end port must be between %d and %d, but got %d", minPort+1, maxPort, endPort)
}

if endPort <= port {
return trace.BadParameter("end port must be greater than port (%d vs %d)", endPort, port)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if endPort != 0 {
if endPort < minPort+1 || endPort > maxPort {
return trace.BadParameter("end port must be between %d and %d, but got %d", minPort+1, maxPort, endPort)
}
if endPort <= port {
return trace.BadParameter("end port must be greater than port (%d vs %d)", endPort, port)
}
}
if endPort != 0 {
if endPort <= port || endPort > maxPort {
return trace.BadParameter("end port must be between %d and %d, but got %d", port+1, maxPort, endPort)
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do-not-merge documentation no-changelog Indicates that a PR does not require a changelog entry size/lg
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants