Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/proto/teleport/legacy/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2040,6 +2040,10 @@ message ProvisionTokenSpecV2Oracle {
// full region names ("us-phoenix-1") and abbreviations ("phx") are allowed.
// If empty, any region is allowed.
repeated string Regions = 3 [(gogoproto.jsontag) = "regions,omitempty"];
// Instances is a list of the OCIDs of specific instances that are allowed
// to join. If empty, any instance matching the other fields in the rule is allowed.
// Limited to 100 instance OCIDs per rule.
repeated string Instances = 4 [(gogoproto.jsontag) = "instances,omitempty"];
Copy link
Copy Markdown
Contributor

@rosstimothy rosstimothy Nov 3, 2025

Choose a reason for hiding this comment

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

Is there an upper bound that we will enforce?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

on the length of this list? I haven't put an explicit upper bound. This field is also within a list of oracle allow rules with no explicit length limit. I'm not sure a list is even the best option tbh, I think this would mostly be useful for

a) automation that creates a token for a specific instance
b) some sort of discover flow for a adding a single oci instance

@rosstimothy what do you think, I could make it a single string per oracle allow rule? or add some arbitrary upper bound like 100

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hrm good question. From a UX perspective it seems like a single rule which limits N instances in the same compartment, regions, etc would be less work than a new rule per instance.

If we don't enforce any limitations here, gRPC will eventually do that for us.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added an explicit cap of 100 instance IDs per rule

}
// Allow is a list of Rules, nodes using this token must match one
// allow rule to use this token.
Expand Down
3 changes: 3 additions & 0 deletions api/types/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,9 @@ func (a *ProvisionTokenSpecV2Oracle) checkAndSetDefaults() error {
i,
)
}
if len(rule.Instances) > 100 {
return trace.BadParameter("allow[%d]: maximum 100 instances may be set (found %d)", i, len(rule.Instances))
}
}
return nil
}
Expand Down
30 changes: 30 additions & 0 deletions api/types/provisioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package types

import (
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -1646,6 +1647,27 @@ func TestProvisionTokenV2_CheckAndSetDefaults(t *testing.T) {
},
wantErr: true,
},
{
desc: "oracle too many instance IDs",
token: &ProvisionTokenV2{
Metadata: Metadata{
Name: "test",
},
Spec: ProvisionTokenSpecV2{
Roles: []SystemRole{RoleNode},
JoinMethod: JoinMethodOracle,
Oracle: &ProvisionTokenSpecV2Oracle{
Allow: []*ProvisionTokenSpecV2Oracle_Rule{
{
Tenancy: "ocid.tenancy.oc1..mytentant",
Instances: genOCIDs(101),
},
},
},
},
},
wantErr: true,
},
}

for _, tc := range testcases {
Expand All @@ -1667,6 +1689,14 @@ func TestProvisionTokenV2_CheckAndSetDefaults(t *testing.T) {
}
}

func genOCIDs(count int) []string {
out := make([]string, count)
for i := range count {
out[i] = fmt.Sprintf("ocid.instance.oc1.region.%d", i)
}
return out
}

func TestProvisionTokenV2_GetSafeName(t *testing.T) {
t.Run("token join method (short)", func(t *testing.T) {
tok, err := NewProvisionToken("1234", []SystemRole{RoleNode}, time.Now())
Expand Down
4,368 changes: 2,210 additions & 2,158 deletions api/types/types.pb.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ resource, which you can apply after installing the Teleport Kubernetes operator.

|Field|Type|Description|
|---|---|---|
|instances|[]string||
|parent_compartments|[]string||
|regions|[]string||
|tenancy|string||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ Optional:

Optional:

- `instances` (List of String) Instances is a list of the OCIDs of specific instances that are allowed to join. If empty, any instance matching the other fields in the rule is allowed. Limited to 100 instance OCIDs per rule.
- `parent_compartments` (List of String) ParentCompartments is a list of the OCIDs of compartments an instance is allowed to join from. Only direct parents are allowed, i.e. no nested compartments. If empty, any compartment is allowed.
- `regions` (List of String) Regions is a list of regions an instance is allowed to join from. Both full region names ("us-phoenix-1") and abbreviations ("phx") are allowed. If empty, any region is allowed.
- `tenancy` (String) Tenancy is the OCID of the instance's tenancy. Required.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ Optional:

Optional:

- `instances` (List of String) Instances is a list of the OCIDs of specific instances that are allowed to join. If empty, any instance matching the other fields in the rule is allowed. Limited to 100 instance OCIDs per rule.
- `parent_compartments` (List of String) ParentCompartments is a list of the OCIDs of compartments an instance is allowed to join from. Only direct parents are allowed, i.e. no nested compartments. If empty, any compartment is allowed.
- `regions` (List of String) Regions is a list of regions an instance is allowed to join from. Both full region names ("us-phoenix-1") and abbreviations ("phx") are allowed. If empty, any region is allowed.
- `tenancy` (String) Tenancy is the OCID of the instance's tenancy. Required.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,11 @@ spec:
must match one allow rule to use this token.
items:
properties:
instances:
items:
type: string
nullable: true
type: array
parent_compartments:
items:
type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,11 @@ spec:
must match one allow rule to use this token.
items:
properties:
instances:
items:
type: string
nullable: true
type: array
parent_compartments:
items:
type: string
Expand Down
85 changes: 85 additions & 0 deletions integrations/terraform/tfschema/types_terraform.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2460,6 +2460,11 @@ func validateOracleJoinToken(token types.ProvisionToken) error {
return trace.BadParameter("invalid region: %v", region)
}
}
for _, instanceID := range allow.Instances {
if _, err := oracle.ParseRegionFromOCID(instanceID); err != nil {
return trace.BadParameter("invalid instance OCID: %s", instanceID)
}
}
}
return nil
}
Expand Down
96 changes: 96 additions & 0 deletions lib/auth/join_oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/api/client/proto"
Expand Down Expand Up @@ -143,6 +144,101 @@ func mapFromHeader(header http.Header) map[string]string {
return out
}

func TestOracleTokenValidation(t *testing.T) {
t.Parallel()
server, err := authtest.NewTestServer(authtest.ServerConfig{
Auth: authtest.AuthServerConfig{
Dir: t.TempDir(),
},
})
require.NoError(t, err)
a, err := server.NewClient(authtest.TestAdmin())
require.NoError(t, err)

for _, tc := range []struct {
desc string
rule types.ProvisionTokenSpecV2Oracle_Rule
assertion assert.ErrorAssertionFunc
}{
{
desc: "valid",
rule: types.ProvisionTokenSpecV2Oracle_Rule{
Tenancy: "ocid1.tenancy.oc1..mytenant",
ParentCompartments: []string{"ocid1.compartment.oc1..mycompartment"},
Regions: []string{"us-phoenix-1"},
Instances: []string{"ocid1.instance.oc1.phx.myinstance"},
},
assertion: assert.NoError,
},
{
desc: "invalid tenant",
rule: types.ProvisionTokenSpecV2Oracle_Rule{
Tenancy: "ocid1.badtenancy.oc1..mytenant",
ParentCompartments: []string{"ocid1.compartment.oc1..mycompartment"},
Regions: []string{"us-phoenix-1"},
Instances: []string{"ocid1.instance.oc1.phx.myinstance"},
},
assertion: func(t assert.TestingT, err error, msgAndArgs ...any) bool {
return assert.ErrorAs(t, err, new(*trace.BadParameterError), msgAndArgs...) &&
assert.ErrorContains(t, err, "invalid tenant", msgAndArgs...)
},
},
{
desc: "invalid compartment",
rule: types.ProvisionTokenSpecV2Oracle_Rule{
Tenancy: "ocid1.tenancy.oc1..mytenant",
ParentCompartments: []string{"ocid1.badcompartment.oc1..mycompartment"},
Regions: []string{"us-phoenix-1"},
Instances: []string{"ocid1.instance.oc1.phx.myinstance"},
},
assertion: func(t assert.TestingT, err error, msgAndArgs ...any) bool {
return assert.ErrorAs(t, err, new(*trace.BadParameterError), msgAndArgs...) &&
assert.ErrorContains(t, err, "invalid compartment", msgAndArgs...)
},
},
{
desc: "invalid region",
rule: types.ProvisionTokenSpecV2Oracle_Rule{
Tenancy: "ocid1.tenancy.oc1..mytenant",
ParentCompartments: []string{"ocid1.compartment.oc1..mycompartment"},
Regions: []string{"badregion"},
Instances: []string{"ocid1.instance.oc1.phx.myinstance"},
},
assertion: func(t assert.TestingT, err error, msgAndArgs ...any) bool {
return assert.ErrorAs(t, err, new(*trace.BadParameterError), msgAndArgs...) &&
assert.ErrorContains(t, err, "invalid region", msgAndArgs...)
},
},
{
desc: "invalid instance",
rule: types.ProvisionTokenSpecV2Oracle_Rule{
Tenancy: "ocid1.tenancy.oc1..mytenant",
ParentCompartments: []string{"ocid1.compartment.oc1..mycompartment"},
Regions: []string{"us-phoenix-1"},
Instances: []string{"ocid1.badinstance.oc1.phx.myinstance"},
},
assertion: func(t assert.TestingT, err error, msgAndArgs ...any) bool {
return assert.ErrorAs(t, err, new(*trace.BadParameterError), msgAndArgs...) &&
assert.ErrorContains(t, err, "invalid instance", msgAndArgs...)
},
},
} {
t.Run(tc.desc, func(t *testing.T) {
tokenSpec := types.ProvisionTokenSpecV2{
Roles: []types.SystemRole{types.RoleNode},
JoinMethod: types.JoinMethodOracle,
Oracle: &types.ProvisionTokenSpecV2Oracle{
Allow: []*types.ProvisionTokenSpecV2Oracle_Rule{&tc.rule},
},
}
token, err := types.NewProvisionTokenFromSpec("my-token", time.Now().Add(time.Minute), tokenSpec)
require.NoError(t, err)
err = a.UpsertToken(t.Context(), token)
tc.assertion(t, err)
})
}
}

func TestRegisterUsingOracleMethod(t *testing.T) {
t.Parallel()
ctx := t.Context()
Expand Down
Loading
Loading