add option to allow client redirects from IPs in specified CIDR ranges in SSO client logins (#44556)

Co-authored-by: Andrew LeFevre <Andrew LeFevre>
This commit is contained in:
Andrew LeFevre
2024-07-30 17:02:57 -04:00
committed by GitHub
parent ee29097823
commit bca2943dca
30 changed files with 1342 additions and 743 deletions
@@ -14,7 +14,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.4.0
// - protoc-gen-go-grpc v1.5.0
// - protoc (unknown)
// source: teleport/userprovisioning/v1/statichostuser_service.proto
@@ -30,8 +30,8 @@ import (
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.62.0 or later.
const _ = grpc.SupportPackageIsVersion8
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
StaticHostUsersService_GetStaticHostUser_FullMethodName = "/teleport.userprovisioning.v1.StaticHostUsersService/GetStaticHostUser"
@@ -132,7 +132,7 @@ func (c *staticHostUsersServiceClient) DeleteStaticHostUser(ctx context.Context,
// StaticHostUsersServiceServer is the server API for StaticHostUsersService service.
// All implementations must embed UnimplementedStaticHostUsersServiceServer
// for forward compatibility
// for forward compatibility.
//
// StaticHostUsersService provides methods to manage static host users.
type StaticHostUsersServiceServer interface {
@@ -151,9 +151,12 @@ type StaticHostUsersServiceServer interface {
mustEmbedUnimplementedStaticHostUsersServiceServer()
}
// UnimplementedStaticHostUsersServiceServer must be embedded to have forward compatible implementations.
type UnimplementedStaticHostUsersServiceServer struct {
}
// UnimplementedStaticHostUsersServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedStaticHostUsersServiceServer struct{}
func (UnimplementedStaticHostUsersServiceServer) GetStaticHostUser(context.Context, *GetStaticHostUserRequest) (*StaticHostUser, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStaticHostUser not implemented")
@@ -175,6 +178,7 @@ func (UnimplementedStaticHostUsersServiceServer) DeleteStaticHostUser(context.Co
}
func (UnimplementedStaticHostUsersServiceServer) mustEmbedUnimplementedStaticHostUsersServiceServer() {
}
func (UnimplementedStaticHostUsersServiceServer) testEmbeddedByValue() {}
// UnsafeStaticHostUsersServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to StaticHostUsersServiceServer will
@@ -184,6 +188,13 @@ type UnsafeStaticHostUsersServiceServer interface {
}
func RegisterStaticHostUsersServiceServer(s grpc.ServiceRegistrar, srv StaticHostUsersServiceServer) {
// If the following call pancis, it indicates UnimplementedStaticHostUsersServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&StaticHostUsersService_ServiceDesc, srv)
}
@@ -4436,6 +4436,8 @@ message MaxAge {
message SSOClientRedirectSettings {
// a list of hostnames allowed for https client redirect URLs
repeated string allowed_https_hostnames = 1;
// a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
repeated string insecure_allowed_cidr_ranges = 2;
}
// OIDCAuthRequest is a request to authenticate with OIDC
+23
View File
@@ -17,6 +17,7 @@ limitations under the License.
package types
import (
"net/netip"
"net/url"
"slices"
"time"
@@ -35,6 +36,11 @@ type OIDCConnector interface {
// ResourceWithSecrets provides common methods for objects
ResourceWithSecrets
ResourceWithOrigin
// Validate will preform checks not found in CheckAndSetDefaults
// that should only be preformed when the OIDC connector resource
// itself is being created or updated, not when a OIDCConnector
// object is being created or updated.
Validate() error
// Issuer URL is the endpoint of the provider, e.g. https://accounts.google.com
GetIssuerURL() string
// ClientID is id for authentication client (in our case it's our Auth server)
@@ -449,6 +455,23 @@ func (o *OIDCConnectorV3) CheckAndSetDefaults() error {
return nil
}
// Validate will preform checks not found in CheckAndSetDefaults
// that should only be preformed when the OIDC connector resource
// itself is being created or updated, not when a OIDCConnector
// object is being created or updated.
func (o *OIDCConnectorV3) Validate() error {
if o.Spec.ClientRedirectSettings != nil {
for _, cidrStr := range o.Spec.ClientRedirectSettings.InsecureAllowedCidrRanges {
_, err := netip.ParsePrefix(cidrStr)
if err != nil {
return trace.BadParameter("bad CIDR range in insecure_allowed_cidr_ranges '%s': %v", cidrStr, err)
}
}
}
return nil
}
// GetAllowUnverifiedEmail returns true if unverified emails should be allowed in received users.
func (o *OIDCConnectorV3) GetAllowUnverifiedEmail() bool {
return o.Spec.AllowUnverifiedEmail
+743 -692
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -197,6 +197,35 @@ authentication succeeds, Teleport will retrieve SSH and X.509 certificates and
store them in the `~/.tsh/keys/<clustername>` directory. The tool will also will
add SSH cert to an SSH agent if there's one running.
### Changing Callback Address
The callback address can be changed if calling back to a remote machine
instead of the local machine is required:
```code
# --bind-addr sets the host and port tsh will listen on, and --callback changes
# what link is displayed to the user
$ tsh login --proxy=proxy.example.com --auth=github --bind-addr=localhost:1234 --callback https://remote.machine:1234
```
For this to work the hostname or CIDR of the remote machine that will be used for
the callback will need to be allowed via`spec.client_redirect_settings`:
```code
spec:
client_redirect_settings:
# a list of hostnames allowed for HTTPS client redirect URLs
# can be a regex pattern
allowed_https_hostnames:
- remote.machine
- '*.app.github.dev'
- '^\d+-[a-zA-Z0-9]+\.foo.internal$'
# a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
insecure_allowed_cidr_ranges:
- '192.168.1.0/24'
- '2001:db8::/96'
```
## Configuring SSO
Teleport works with SSO providers by relying on the concept of an
+1
View File
@@ -316,6 +316,7 @@ $ tsh login [<flags>] [<cluster>]
| Name | Default Value(s) | Allowed Value(s) | Description |
| - | - | - | - |
| `--bind-addr` | none | host:port | Address in the form of host:port to bind to for login command webhook |
| `--callback` | none | host:port | Override the base URL (host:port) of the link shown when opening a browser for cluster logins. Must be used with --bind-addr.
| `-o, --out` | none | filepath | Identity output filepath |
| `--format` | `file` | `file`, `openssh` or `kubernetes` | Identity format: file, openssh (for OpenSSH compatibility) or kubernetes (for kubeconfig) |
| `--browser` | none | `none` | Set to 'none' to suppress opening system default browser for `tsh login` commands |
@@ -45,6 +45,7 @@ Optional:
Optional:
- `allowed_https_hostnames` (List of String) a list of hostnames allowed for https client redirect URLs
- `insecure_allowed_cidr_ranges` (List of String) a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
### Nested Schema for `spec.teams_to_logins`
@@ -59,6 +59,7 @@ Optional:
Optional:
- `allowed_https_hostnames` (List of String) a list of hostnames allowed for https client redirect URLs
- `insecure_allowed_cidr_ranges` (List of String) a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
@@ -69,6 +69,7 @@ Optional:
Optional:
- `allowed_https_hostnames` (List of String) a list of hostnames allowed for https client redirect URLs
- `insecure_allowed_cidr_ranges` (List of String) a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
### Nested Schema for `spec.signing_key_pair`
@@ -76,6 +76,7 @@ Optional:
Optional:
- `allowed_https_hostnames` (List of String) a list of hostnames allowed for https client redirect URLs
- `insecure_allowed_cidr_ranges` (List of String) a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
### Nested Schema for `spec.teams_to_logins`
@@ -89,6 +89,7 @@ Optional:
Optional:
- `allowed_https_hostnames` (List of String) a list of hostnames allowed for https client redirect URLs
- `insecure_allowed_cidr_ranges` (List of String) a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
@@ -115,6 +115,7 @@ Optional:
Optional:
- `allowed_https_hostnames` (List of String) a list of hostnames allowed for https client redirect URLs
- `insecure_allowed_cidr_ranges` (List of String) a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
### Nested Schema for `spec.signing_key_pair`
@@ -55,6 +55,13 @@ spec:
type: string
nullable: true
type: array
insecure_allowed_cidr_ranges:
description: a list of CIDRs allowed for HTTP or HTTPS client
redirect URLs
items:
type: string
nullable: true
type: array
type: object
client_secret:
description: ClientSecret is the Github OAuth app client secret.
@@ -80,6 +80,13 @@ spec:
type: string
nullable: true
type: array
insecure_allowed_cidr_ranges:
description: a list of CIDRs allowed for HTTP or HTTPS client
redirect URLs
items:
type: string
nullable: true
type: array
type: object
client_secret:
description: ClientSecret is used to authenticate the client.
@@ -95,6 +95,13 @@ spec:
type: string
nullable: true
type: array
insecure_allowed_cidr_ranges:
description: a list of CIDRs allowed for HTTP or HTTPS client
redirect URLs
items:
type: string
nullable: true
type: array
type: object
display:
description: Display controls how this connector is displayed.
+12
View File
@@ -62,3 +62,15 @@ spec:
- name: "http://schemas.xmlsoap.org/claims/Group"
value: "Users"
roles: ["access"]
client_redirect_settings:
# a list of hostnames allowed for HTTPS client redirect URLs
# can be a regex pattern
allowed_https_hostnames:
- remote.machine
- '*.app.github.dev'
- '^\d+-[a-zA-Z0-9]+\.foo.internal$'
# a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
insecure_allowed_cidr_ranges:
- '192.168.1.0/24'
- '2001:db8::/96'
+11
View File
@@ -20,3 +20,14 @@ spec:
- editor
organization: <github-org>
team: <github-team>
client_redirect_settings:
# a list of hostnames allowed for HTTPS client redirect URLs
# can be a regex pattern
allowed_https_hostnames:
- remote.machine
- '*.app.github.dev'
- '^\d+-[a-zA-Z0-9]+\.foo.internal$'
# a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
insecure_allowed_cidr_ranges:
- '192.168.1.0/24'
- '2001:db8::/96'
@@ -33,4 +33,15 @@ spec:
scope:
- openid
- email
client_redirect_settings:
# a list of hostnames allowed for HTTPS client redirect URLs
# can be a regex pattern
allowed_https_hostnames:
- remote.machine
- '*.app.github.dev'
- '^\d+-[a-zA-Z0-9]+\.foo.internal$'
# a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
insecure_allowed_cidr_ranges:
- '192.168.1.0/24'
- '2001:db8::/96'
version: v3
@@ -21,4 +21,15 @@ spec:
scope:
- openid
- email
client_redirect_settings:
# a list of hostnames allowed for HTTPS client redirect URLs
# can be a regex pattern
allowed_https_hostnames:
- remote.machine
- '*.app.github.dev'
- '^\d+-[a-zA-Z0-9]+\.foo.internal$'
# a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
insecure_allowed_cidr_ranges:
- '192.168.1.0/24'
- '2001:db8::/96'
version: v3
+11
View File
@@ -16,4 +16,15 @@ spec:
issuer_url: https://idp.example.com/
redirect_url: https://mytenant.teleport.sh:443/v1/webapi/oidc/callback
max_age: 24h
client_redirect_settings:
# a list of hostnames allowed for HTTPS client redirect URLs
# can be a regex pattern
allowed_https_hostnames:
- remote.machine
- '*.app.github.dev'
- '^\d+-[a-zA-Z0-9]+\.foo.internal$'
# a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
insecure_allowed_cidr_ranges:
- '192.168.1.0/24'
- '2001:db8::/96'
version: v3
@@ -23,4 +23,15 @@ spec:
issuer: ""
service_provider_issuer: https://teleport.example.com:443/v1/webapi/saml/acs/onelogin
sso: ""
client_redirect_settings:
# a list of hostnames allowed for HTTPS client redirect URLs
# can be a regex pattern
allowed_https_hostnames:
- remote.machine
- '*.app.github.dev'
- '^\d+-[a-zA-Z0-9]+\.foo.internal$'
# a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
insecure_allowed_cidr_ranges:
- '192.168.1.0/24'
- '2001:db8::/96'
version: v2
+11
View File
@@ -31,4 +31,15 @@ spec:
# Optional SAML Single Logout endpoint. If set, logging out of Teleport
# will also log the user out of the SAML provider session.
single_logout_url: https://example.okta.com/app/your-app-id/slo/saml
client_redirect_settings:
# a list of hostnames allowed for HTTPS client redirect URLs
# can be a regex pattern
allowed_https_hostnames:
- remote.machine
- '*.app.github.dev'
- '^\d+-[a-zA-Z0-9]+\.foo.internal$'
# a list of CIDRs allowed for HTTP or HTTPS client redirect URLs
insecure_allowed_cidr_ranges:
- '192.168.1.0/24'
- '2001:db8::/96'
@@ -55,6 +55,13 @@ spec:
type: string
nullable: true
type: array
insecure_allowed_cidr_ranges:
description: a list of CIDRs allowed for HTTP or HTTPS client
redirect URLs
items:
type: string
nullable: true
type: array
type: object
client_secret:
description: ClientSecret is the Github OAuth app client secret.
@@ -80,6 +80,13 @@ spec:
type: string
nullable: true
type: array
insecure_allowed_cidr_ranges:
description: a list of CIDRs allowed for HTTP or HTTPS client
redirect URLs
items:
type: string
nullable: true
type: array
type: object
client_secret:
description: ClientSecret is used to authenticate the client.
@@ -95,6 +95,13 @@ spec:
type: string
nullable: true
type: array
insecure_allowed_cidr_ranges:
description: a list of CIDRs allowed for HTTP or HTTPS client
redirect URLs
items:
type: string
nullable: true
type: array
type: object
display:
description: Display controls how this connector is displayed.
@@ -2709,11 +2709,18 @@ func GenSchemaOIDCConnectorV3(ctx context.Context) (github_com_hashicorp_terrafo
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
"client_redirect_settings": {
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"allowed_https_hostnames": {
Description: "a list of hostnames allowed for https client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
}}),
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{
"allowed_https_hostnames": {
Description: "a list of hostnames allowed for https client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
},
"insecure_allowed_cidr_ranges": {
Description: "a list of CIDRs allowed for HTTP or HTTPS client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
},
}),
Description: "ClientRedirectSettings defines which client redirect URLs are allowed for non-browser SSO logins other than the standard localhost ones.",
Optional: true,
},
@@ -2918,11 +2925,18 @@ func GenSchemaSAMLConnectorV2(ctx context.Context) (github_com_hashicorp_terrafo
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
"client_redirect_settings": {
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"allowed_https_hostnames": {
Description: "a list of hostnames allowed for https client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
}}),
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{
"allowed_https_hostnames": {
Description: "a list of hostnames allowed for https client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
},
"insecure_allowed_cidr_ranges": {
Description: "a list of CIDRs allowed for HTTP or HTTPS client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
},
}),
Description: "ClientRedirectSettings defines which client redirect URLs are allowed for non-browser SSO logins other than the standard localhost ones.",
Optional: true,
},
@@ -3085,11 +3099,18 @@ func GenSchemaGithubConnectorV3(ctx context.Context) (github_com_hashicorp_terra
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
"client_redirect_settings": {
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"allowed_https_hostnames": {
Description: "a list of hostnames allowed for https client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
}}),
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{
"allowed_https_hostnames": {
Description: "a list of hostnames allowed for https client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
},
"insecure_allowed_cidr_ranges": {
Description: "a list of CIDRs allowed for HTTP or HTTPS client redirect URLs",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType},
},
}),
Description: "ClientRedirectSettings defines which client redirect URLs are allowed for non-browser SSO logins other than the standard localhost ones.",
Optional: true,
},
@@ -28154,6 +28175,33 @@ func CopyOIDCConnectorV3FromTerraform(_ context.Context, tf github_com_hashicorp
}
}
}
{
a, ok := tf.Attrs["insecure_allowed_cidr_ranges"]
if !ok {
diags.Append(attrReadMissingDiag{"OIDCConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges"})
} else {
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List)
if !ok {
diags.Append(attrReadConversionFailureDiag{"OIDCConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.List"})
} else {
obj.InsecureAllowedCidrRanges = make([]string, len(v.Elems))
if !v.Null && !v.Unknown {
for k, a := range v.Elems {
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
diags.Append(attrReadConversionFailureDiag{"OIDCConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github_com_hashicorp_terraform_plugin_framework_types.String"})
} else {
var t string
if !v.Null && !v.Unknown {
t = string(v.Value)
}
obj.InsecureAllowedCidrRanges[k] = t
}
}
}
}
}
}
}
}
}
@@ -29042,6 +29090,59 @@ func CopyOIDCConnectorV3ToTerraform(ctx context.Context, obj *github_com_gravita
}
}
}
{
a, ok := tf.AttrTypes["insecure_allowed_cidr_ranges"]
if !ok {
diags.Append(attrWriteMissingDiag{"OIDCConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges"})
} else {
o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType)
if !ok {
diags.Append(attrWriteConversionFailureDiag{"OIDCConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.ListType"})
} else {
c, ok := tf.Attrs["insecure_allowed_cidr_ranges"].(github_com_hashicorp_terraform_plugin_framework_types.List)
if !ok {
c = github_com_hashicorp_terraform_plugin_framework_types.List{
ElemType: o.ElemType,
Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges)),
Null: true,
}
} else {
if c.Elems == nil {
c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges))
}
}
if obj.InsecureAllowedCidrRanges != nil {
t := o.ElemType
if len(obj.InsecureAllowedCidrRanges) != len(c.Elems) {
c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges))
}
for k, a := range obj.InsecureAllowedCidrRanges {
v, ok := tf.Attrs["insecure_allowed_cidr_ranges"].(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
if err != nil {
diags.Append(attrWriteGeneralError{"OIDCConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", err})
}
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
diags.Append(attrWriteConversionFailureDiag{"OIDCConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.String"})
}
v.Null = string(a) == ""
}
v.Value = string(a)
v.Unknown = false
c.Elems[k] = v
}
if len(obj.InsecureAllowedCidrRanges) > 0 {
c.Null = false
}
}
c.Unknown = false
tf.Attrs["insecure_allowed_cidr_ranges"] = c
}
}
}
}
v.Unknown = false
tf.Attrs["client_redirect_settings"] = v
@@ -29675,6 +29776,33 @@ func CopySAMLConnectorV2FromTerraform(_ context.Context, tf github_com_hashicorp
}
}
}
{
a, ok := tf.Attrs["insecure_allowed_cidr_ranges"]
if !ok {
diags.Append(attrReadMissingDiag{"SAMLConnectorV2.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges"})
} else {
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List)
if !ok {
diags.Append(attrReadConversionFailureDiag{"SAMLConnectorV2.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.List"})
} else {
obj.InsecureAllowedCidrRanges = make([]string, len(v.Elems))
if !v.Null && !v.Unknown {
for k, a := range v.Elems {
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
diags.Append(attrReadConversionFailureDiag{"SAMLConnectorV2.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github_com_hashicorp_terraform_plugin_framework_types.String"})
} else {
var t string
if !v.Null && !v.Unknown {
t = string(v.Value)
}
obj.InsecureAllowedCidrRanges[k] = t
}
}
}
}
}
}
}
}
}
@@ -30622,6 +30750,59 @@ func CopySAMLConnectorV2ToTerraform(ctx context.Context, obj *github_com_gravita
}
}
}
{
a, ok := tf.AttrTypes["insecure_allowed_cidr_ranges"]
if !ok {
diags.Append(attrWriteMissingDiag{"SAMLConnectorV2.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges"})
} else {
o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType)
if !ok {
diags.Append(attrWriteConversionFailureDiag{"SAMLConnectorV2.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.ListType"})
} else {
c, ok := tf.Attrs["insecure_allowed_cidr_ranges"].(github_com_hashicorp_terraform_plugin_framework_types.List)
if !ok {
c = github_com_hashicorp_terraform_plugin_framework_types.List{
ElemType: o.ElemType,
Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges)),
Null: true,
}
} else {
if c.Elems == nil {
c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges))
}
}
if obj.InsecureAllowedCidrRanges != nil {
t := o.ElemType
if len(obj.InsecureAllowedCidrRanges) != len(c.Elems) {
c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges))
}
for k, a := range obj.InsecureAllowedCidrRanges {
v, ok := tf.Attrs["insecure_allowed_cidr_ranges"].(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
if err != nil {
diags.Append(attrWriteGeneralError{"SAMLConnectorV2.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", err})
}
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
diags.Append(attrWriteConversionFailureDiag{"SAMLConnectorV2.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.String"})
}
v.Null = string(a) == ""
}
v.Value = string(a)
v.Unknown = false
c.Elems[k] = v
}
if len(obj.InsecureAllowedCidrRanges) > 0 {
c.Null = false
}
}
c.Unknown = false
tf.Attrs["insecure_allowed_cidr_ranges"] = c
}
}
}
}
v.Unknown = false
tf.Attrs["client_redirect_settings"] = v
@@ -31231,6 +31412,33 @@ func CopyGithubConnectorV3FromTerraform(_ context.Context, tf github_com_hashico
}
}
}
{
a, ok := tf.Attrs["insecure_allowed_cidr_ranges"]
if !ok {
diags.Append(attrReadMissingDiag{"GithubConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges"})
} else {
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List)
if !ok {
diags.Append(attrReadConversionFailureDiag{"GithubConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.List"})
} else {
obj.InsecureAllowedCidrRanges = make([]string, len(v.Elems))
if !v.Null && !v.Unknown {
for k, a := range v.Elems {
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
diags.Append(attrReadConversionFailureDiag{"GithubConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github_com_hashicorp_terraform_plugin_framework_types.String"})
} else {
var t string
if !v.Null && !v.Unknown {
t = string(v.Value)
}
obj.InsecureAllowedCidrRanges[k] = t
}
}
}
}
}
}
}
}
}
@@ -32158,6 +32366,59 @@ func CopyGithubConnectorV3ToTerraform(ctx context.Context, obj *github_com_gravi
}
}
}
{
a, ok := tf.AttrTypes["insecure_allowed_cidr_ranges"]
if !ok {
diags.Append(attrWriteMissingDiag{"GithubConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges"})
} else {
o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType)
if !ok {
diags.Append(attrWriteConversionFailureDiag{"GithubConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.ListType"})
} else {
c, ok := tf.Attrs["insecure_allowed_cidr_ranges"].(github_com_hashicorp_terraform_plugin_framework_types.List)
if !ok {
c = github_com_hashicorp_terraform_plugin_framework_types.List{
ElemType: o.ElemType,
Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges)),
Null: true,
}
} else {
if c.Elems == nil {
c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges))
}
}
if obj.InsecureAllowedCidrRanges != nil {
t := o.ElemType
if len(obj.InsecureAllowedCidrRanges) != len(c.Elems) {
c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.InsecureAllowedCidrRanges))
}
for k, a := range obj.InsecureAllowedCidrRanges {
v, ok := tf.Attrs["insecure_allowed_cidr_ranges"].(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
if err != nil {
diags.Append(attrWriteGeneralError{"GithubConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", err})
}
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
diags.Append(attrWriteConversionFailureDiag{"GithubConnectorV3.Spec.ClientRedirectSettings.insecure_allowed_cidr_ranges", "github.com/hashicorp/terraform-plugin-framework/types.String"})
}
v.Null = string(a) == ""
}
v.Value = string(a)
v.Unknown = false
c.Elems[k] = v
}
if len(obj.InsecureAllowedCidrRanges) > 0 {
c.Null = false
}
}
c.Unknown = false
tf.Attrs["insecure_allowed_cidr_ranges"] = c
}
}
}
}
v.Unknown = false
tf.Attrs["client_redirect_settings"] = v
+52 -28
View File
@@ -24,8 +24,11 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/netip"
"net/url"
"slices"
"strings"
"time"
@@ -909,6 +912,8 @@ func (a *Server) createGithubUser(ctx context.Context, p *CreateUserParams, dryR
return user, nil
}
const unknownRedirectHostnameErrMsg = "unknown custom client redirect URL hostname"
// ValidateClientRedirect checks a desktop client redirect URL for SSO logins
// against some (potentially nil) settings from an auth connector; in the
// current implementation, that means either "http" schema with a hostname of
@@ -916,7 +921,10 @@ func (a *Server) createGithubUser(ctx context.Context, p *CreateUserParams, dryR
// or "https" schema with a hostname that matches one in the https_hostname
// list, a path of "/callback" and either an empty port or explicitly 443. The
// settings are ignored and only localhost URLs are allowed if we're using an
// ephemeral connector (in the SSO testing flow).
// ephemeral connector (in the SSO testing flow). If the insecure_allowed_cidr_ranges
// list is non-empty URLs in both the "http" and "https" schema are allowed
// if the hostname is an IP address that is contained in a specified CIDR
// range on any port.
func ValidateClientRedirect(clientRedirect string, ssoTestFlow bool, settings *types.SSOClientRedirectSettings) error {
if clientRedirect == "" {
// empty redirects are non-functional and harmless, so we allow them as
@@ -946,45 +954,61 @@ func ValidateClientRedirect(clientRedirect string, ssoTestFlow bool, settings *t
}
// we checked everything but u.Scheme and u.Host now
switch u.Scheme {
default:
if u.Scheme != "http" && u.Scheme != "https" {
return trace.BadParameter("invalid scheme in client redirect URL")
}
case "http":
switch u.Hostname() {
default:
return trace.BadParameter("invalid hostname in client redirect URL")
case "localhost", "127.0.0.1", "::1":
return nil
}
case "https":
if ssoTestFlow {
return trace.AccessDenied("custom client redirect URLs are not allowed in SSO test")
// allow HTTP redirects to local addresses
allowedHTTPLocalAddrs := []string{"localhost", "127.0.0.1", "::1"}
if u.Scheme == "http" && slices.Contains(allowedHTTPLocalAddrs, u.Hostname()) {
return nil
}
if ssoTestFlow {
return trace.AccessDenied("custom client redirect URLs are not allowed in SSO test")
}
if settings == nil {
return trace.AccessDenied(unknownRedirectHostnameErrMsg)
}
// allow HTTP or HTTPS redirects from IPs in specified CIDR ranges
hostIP, err := netip.ParseAddr(u.Hostname())
if err == nil {
hostIP = hostIP.Unmap()
for _, cidrStr := range settings.InsecureAllowedCidrRanges {
cidr, err := netip.ParsePrefix(cidrStr)
if err != nil {
slog.WarnContext(context.Background(), "error parsing OIDC connector CIDR prefix", "cidr", cidrStr, "err", err)
continue
}
if cidr.Contains(hostIP) {
return nil
}
}
}
if u.Scheme == "https" {
switch u.Port() {
default:
return trace.BadParameter("invalid port in client redirect URL")
case "", "443":
}
var allowedHostnames []string
if settings != nil {
allowedHostnames = settings.AllowedHttpsHostnames
for _, expression := range settings.AllowedHttpsHostnames {
ok, err := utils.MatchString(u.Hostname(), expression)
if err != nil {
slog.WarnContext(context.Background(), "error compiling OIDC connector allowed HTTPS hostname regex", "regex", expression, "err", err)
continue
}
if ok {
return nil
}
}
ok, err := utils.SliceMatchesRegex(u.Hostname(), allowedHostnames)
if err != nil {
return trace.Wrap(err, "matching custom client redirect URL hostname")
}
if !ok {
return trace.AccessDenied("unknown custom client redirect URL hostname")
}
return nil
}
return trace.AccessDenied(unknownRedirectHostnameErrMsg)
}
// populateGithubClaims builds a GithubClaims using queried
+57
View File
@@ -643,6 +643,9 @@ func TestValidateClientRedirect(t *testing.T) {
"https://127.0.0.1:12345/callback",
"https://localhost:12345/callback",
"https://localhost/callback",
"ftp://localhost/callback",
"ftp://127.0.0.1/callback",
"ftp://[::1]/callback",
} {
const ssoTestFlowFalse = false
var defaultSettings *types.SSOClientRedirectSettings
@@ -699,6 +702,60 @@ func TestValidateClientRedirect(t *testing.T) {
}
})
t.Run("InsecureAllowedCidrRanges", func(t *testing.T) {
for _, goodURL := range []string{
"http://192.168.0.27/callback",
"https://192.168.0.27/callback",
"http://192.168.0.27:1337/callback",
"https://192.168.0.27:1337/callback",
"http://[2001:db8::aaaa:bbbb]/callback",
"https://[2001:db8::aaaa:bbbb]/callback",
"http://[2001:db8::aaaa:bbbb]:1337/callback",
"https://[2001:db8::aaaa:bbbb]:1337/callback",
"http://[2001:db8::1]/callback",
"https://[2001:db8::1]/callback",
"http://[2001:db8::1]:1337/callback",
"https://[2001:db8::1]:1337/callback",
} {
const ssoTestFlowFalse = false
settings := &types.SSOClientRedirectSettings{
InsecureAllowedCidrRanges: []string{
"192.168.0.0/24",
"2001:db8::/96",
},
}
require.NoError(t, ValidateClientRedirect(goodURL+"?secret_key=", ssoTestFlowFalse, settings))
}
for _, badURL := range []string{
"http://192.168.1.1/callback",
"https://192.168.1.1/callback",
"http://192.168.1.1:80/callback",
"https://192.168.1.1:443/callback",
"http://[2001:db8::1:aaaa:bbbb]/callback",
"https://[2001:db8::1:aaaa:bbbb]/callback",
"http://[2001:db8::1:aaaa:bbbb]:80/callback",
"https://[2001:db8::1:aaaa:bbbb]:443/callback",
"http://[2001:db9::]/callback",
"https://[2001:db9::]/callback",
"http://not.an.ip/callback",
"https://not.an.ip/callback",
"http://192.168.0.27/nocallback",
"https://192.168.0.27/nocallback",
"http://[2001:db8::1]/notacallback",
"https://[2001:db8::1]/notacallback",
} {
const ssoTestFlowFalse = false
settings := &types.SSOClientRedirectSettings{
InsecureAllowedCidrRanges: []string{
"192.168.0.0/24",
"2001:db8::/96",
},
}
require.Error(t, ValidateClientRedirect(badURL+"?secret_key=", ssoTestFlowFalse, settings))
}
})
t.Run("SSOTestFlow", func(t *testing.T) {
for _, goodURL := range []string{
"http://127.0.0.1:12345/callback",
+6 -1
View File
@@ -135,7 +135,12 @@ func NewRedirector(ctx context.Context, login SSHLoginSSO, config *RedirectorCon
if err != nil {
return nil, trace.Wrap(err)
}
callbackURL.Scheme = "https"
// Default to HTTPS if no scheme is specified.
// This will allow users to specify an insecure HTTP URL but
// the backend will verify if the callback URL is allowed.
if callbackURL.Scheme == "" {
callbackURL.Scheme = "https"
}
callbackAddr = callbackURL.String()
}
+9
View File
@@ -1246,6 +1246,9 @@ func (s *IdentityService) GetMFADevices(ctx context.Context, user string, withSe
// UpsertOIDCConnector upserts OIDC Connector
func (s *IdentityService) UpsertOIDCConnector(ctx context.Context, connector types.OIDCConnector) (types.OIDCConnector, error) {
if err := connector.Validate(); err != nil {
return nil, trace.Wrap(err)
}
rev := connector.GetRevision()
value, err := services.MarshalOIDCConnector(connector)
if err != nil {
@@ -1267,6 +1270,9 @@ func (s *IdentityService) UpsertOIDCConnector(ctx context.Context, connector typ
// CreateOIDCConnector creates a new OIDC connector.
func (s *IdentityService) CreateOIDCConnector(ctx context.Context, connector types.OIDCConnector) (types.OIDCConnector, error) {
if err := connector.Validate(); err != nil {
return nil, trace.Wrap(err)
}
value, err := services.MarshalOIDCConnector(connector)
if err != nil {
return nil, trace.Wrap(err)
@@ -1286,6 +1292,9 @@ func (s *IdentityService) CreateOIDCConnector(ctx context.Context, connector typ
// UpdateOIDCConnector updates an existing OIDC connector.
func (s *IdentityService) UpdateOIDCConnector(ctx context.Context, connector types.OIDCConnector) (types.OIDCConnector, error) {
if err := connector.Validate(); err != nil {
return nil, trace.Wrap(err)
}
value, err := services.MarshalOIDCConnector(connector)
if err != nil {
return nil, trace.Wrap(err)