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
434 changes: 343 additions & 91 deletions api/gen/proto/go/teleport/join/v1/joinservice.pb.go

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions api/proto/teleport/join/v1/joinservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,52 @@ message EC2Init {
bytes document = 2;
}

// OracleInit is sent from the client in response to the ServerInit message for
// the Oracle join method.
//
// The Oracle method join flow is:
// 1. client->server: ClientInit
// 2. client<-server: ServerInit
// 3. client->server: OracleInit
// 4. client<-server: OracleChallenge
// 5. client->server: OracleChallengeSolution
// 6. client<-server: Result
message OracleInit {
// ClientParams holds parameters for the specific type of client trying to join.
ClientParams client_params = 1;
}

// OracleChallenge is sent from the server in response to the OracleInit message from the client.
// The client is expected to respond with a OracleChallengeSolution.
message OracleChallenge {
// Challenge is a a crypto-random string that should be included by the
// client in the OracleChallengeSolution message.
string challenge = 1;
}

// OracleChallengeSolution must be sent from the client in response to the
// OracleChallenge message.
message OracleChallengeSolution {
// Cert is the OCI instance identity certificate, an X509 certificate in PEM format.
bytes cert = 1;
// Intermediate encodes the intermediate CAs that issued the instance
// identity certificate, in PEM format.
bytes intermediate = 2;
// Signature is a signature over the challenge, signed by the private key
// matching the instance identity certificate.
bytes signature = 3;
// SignedRootCaReq is a signed request to the Oracle API for retreiving the
// root CAs that issued the instance identity certificate.
bytes signed_root_ca_req = 4;
}

// ChallengeSolution holds a solution to a challenge issued by the server.
message ChallengeSolution {
oneof payload {
BoundKeypairChallengeSolution bound_keypair_challenge_solution = 1;
BoundKeypairRotationResponse bound_keypair_rotation_response = 2;
IAMChallengeSolution iam_challenge_solution = 3;
OracleChallengeSolution oracle_challenge_solution = 4;
}
}

Expand Down Expand Up @@ -272,6 +312,8 @@ message GivingUp {

// JoinRequest is the message type sent from the joining client to the server.
message JoinRequest {
reserved 8;
reserved "oidc_init";
oneof payload {
ClientInit client_init = 1;
TokenInit token_init = 2;
Expand All @@ -280,6 +322,7 @@ message JoinRequest {
IAMInit iam_init = 5;
GivingUp giving_up = 6;
EC2Init ec2_init = 7;
OracleInit oracle_init = 9;
}
}

Expand All @@ -299,6 +342,7 @@ message Challenge {
BoundKeypairChallenge bound_keypair_challenge = 1;
BoundKeypairRotationRequest bound_keypair_rotation_request = 2;
IAMChallenge iam_challenge = 3;
OracleChallenge oracle_challenge = 4;
}
}

Expand Down
3 changes: 3 additions & 0 deletions lib/auth/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ type APIConfig struct {
// MutateRevocationsServiceConfig is a function that allows to mutate
// the revocation service configuration for testing.
MutateRevocationsServiceConfig func(config *workloadidentityv1.RevocationServiceConfig)
// OracleHTTPClient (optional) overrides the HTTP client used to make
// requests to the Oracle API for the Oracle join method.
OracleHTTPClient utils.HTTPDoClient
}

// CheckAndSetDefaults checks and sets default values
Expand Down
5 changes: 0 additions & 5 deletions lib/auth/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import (
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/userloginstate"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/auth/join/oracle"
"github.com/gravitational/teleport/lib/auth/keystore"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/circleci"
Expand Down Expand Up @@ -394,10 +393,6 @@ func CheckHeaders(headers http.Header, challenge string, clock clockwork.Clock)
return checkHeaders(headers, challenge, clock)
}

func CheckOracleAllowRules(claims oracle.Claims, token string, allowRules []*types.ProvisionTokenSpecV2Oracle_Rule) error {
return checkOracleAllowRules(claims, token, allowRules)
}

type GitHubManager = githubManager
type AttestedData = attestedData
type SignedAttestedData = signedAttestedData
Expand Down
7 changes: 4 additions & 3 deletions lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6094,9 +6094,10 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) {
authpb.RegisterJoinServiceServer(server, legacyJoinServiceServer)

joinv1.RegisterJoinServiceServer(server, join.NewServer(&join.ServerConfig{
Authorizer: cfg.Authorizer,
AuthService: cfg.AuthServer,
FIPS: cfg.AuthServer.fips,
Authorizer: cfg.Authorizer,
AuthService: cfg.AuthServer,
FIPS: cfg.AuthServer.fips,
OracleHTTPClient: cfg.OracleHTTPClient,
}))

integrationServiceServer, err := integrationv1.NewService(&integrationv1.ServiceConfig{
Expand Down
11 changes: 11 additions & 0 deletions lib/auth/join/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ type RegisterParams struct {
// GetInstanceIdentityDocumentFunc overrides the function used to get an
// EC2 Instance Identity Document for the host.
GetInstanceIdentityDocumentFunc func(ctx context.Context) ([]byte, error)
// OracleIMDSClient overrides the HTTP client used to make requests to the
// OCI Instance Metadata Service.
OracleIMDSClient utils.HTTPDoClient
}

func (r *RegisterParams) CheckAndSetDefaults() error {
Expand All @@ -219,6 +222,14 @@ func (r *RegisterParams) CheckAndSetDefaults() error {
r.GetInstanceIdentityDocumentFunc = awsutils.GetRawEC2IdentityDocument
}

if r.OracleIMDSClient == nil {
var err error
r.OracleIMDSClient, err = defaults.HTTPClient()
if err != nil {
return trace.Wrap(err)
}
}

return nil
}

Expand Down
111 changes: 17 additions & 94 deletions lib/auth/join/oracle/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

// Package oracle includes functions and types used by the legacy oracle join
// method, which will be removed in v20.
//
// TODO(nklaassen): DELETE IN 20 when removing the legacy join service.
package oracle

import (
Expand All @@ -31,11 +35,23 @@ import (

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api"
workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/join/oraclejoin"
logutils "github.com/gravitational/teleport/lib/utils/log"
)

// Claims are the claims extracted from the instance certificate.
type Claims = oraclejoin.Claims

var (
// ParseRegion parses a string into a full (not abbreviated) Oracle Cloud
// region. It returns the empty string if the input is not a valid region.
ParseRegion = oraclejoin.ParseRegion
// ParseRegionFromOCID parses an Oracle OCID and returns the embedded region.
// It returns an error if the input is not a valid OCID.
ParseRegionFromOCID = oraclejoin.ParseRegionFromOCID
)

const teleportUserAgent = "teleport/" + api.Version

const (
Expand Down Expand Up @@ -63,37 +79,6 @@ type authenticateClientRequest struct {
Details authenticateClientDetails `contributesTo:"body"`
}

// Claims are the claims returned by the authenticateClient endpoint.
type Claims struct {
// TenancyID is the ID of the instance's tenant.
TenancyID string `json:"tenant_id"`
// CompartmentID is the ID of the instance's compartment.
CompartmentID string `json:"compartment_id"`
// InstanceID is the instance's ID.
InstanceID string `json:"-"`
}

// JoinAttrs returns the protobuf representation of the attested identity.
// This is used for auditing and for evaluation of WorkloadIdentity rules and
// templating.
func (c Claims) JoinAttrs() *workloadidentityv1pb.JoinAttrsOracle {
attrs := &workloadidentityv1pb.JoinAttrsOracle{
TenancyId: c.TenancyID,
CompartmentId: c.CompartmentID,
InstanceId: c.InstanceID,
}
return attrs
}

// Region extracts the region from an instance's claims.
func (c Claims) Region() string {
region, err := ParseRegionFromOCID(c.InstanceID)
if err != nil {
return ""
}
return region
}

type claim struct {
Key string `json:"key"`
Value string `json:"value"`
Expand Down Expand Up @@ -318,65 +303,3 @@ func FetchOraclePrincipalClaims(ctx context.Context, req *http.Request) (Claims,
}
return resp.Result.Principal.getClaims(), nil
}

// Hack: StringToRegion will lazily load regions from a config file if its
// input isn't in its hard-coded list, in a non-threadsafe way. Call it here
// to load the config ahead of time so future calls are threadsafe.
var _ = common.StringToRegion("")

// ParseRegion parses a string into a full (not abbreviated) Oracle Cloud
// region. It returns the empty string if the input is not a valid region.
func ParseRegion(rawRegion string) (region, realm string) {
canonicalRegion := common.StringToRegion(rawRegion)
realm, err := canonicalRegion.RealmID()
if err != nil {
return "", ""
}
return string(canonicalRegion), realm
}

var ociRealms = map[string]struct{}{
"oc1": {}, "oc2": {}, "oc3": {}, "oc4": {}, "oc8": {}, "oc9": {},
"oc10": {}, "oc14": {}, "oc15": {}, "oc19": {}, "oc20": {}, "oc21": {},
"oc23": {}, "oc24": {}, "oc26": {}, "oc29": {}, "oc35": {},
}

// ParseRegionFromOCID parses an Oracle OCID and returns the embedded region.
// It returns an error if the input is not a valid OCID.
func ParseRegionFromOCID(ocid string) (string, error) {
// OCID format: ocid1.<RESOURCE TYPE>.<REALM>.[REGION][.FUTURE USE].<UNIQUE ID>
// Check format.
ocidParts := strings.Split(ocid, ".")
switch len(ocidParts) {
case 5, 6:
default:
return "", trace.BadParameter("not an ocid")
}
// Check version.
if ocidParts[0] != "ocid1" {
return "", trace.BadParameter("invalid ocid version: %v", ocidParts[0])
}
// Check realm.
if _, ok := ociRealms[ocidParts[2]]; !ok {
return "", trace.BadParameter("invalid realm: %v", ocidParts[2])
}
resourceType := ocidParts[1]
region, realm := ParseRegion(ocidParts[3])
// Check type. Only instance OCIDs should have a region.
switch resourceType {
case "instance":
if region == "" {
return "", trace.BadParameter("invalid region: %v", region)
}
if realm != ocidParts[2] {
return "", trace.BadParameter("invalid realm %q for region %q", ocidParts[2], region)
}
case "compartment", "tenancy":
if ocidParts[3] != "" {
return "", trace.BadParameter("resource type %v should not have a region", resourceType)
}
default:
return "", trace.BadParameter("unsupported resource type: %v", resourceType)
}
return region, nil
}
34 changes: 7 additions & 27 deletions lib/auth/join_oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ import (
"github.com/gravitational/teleport/lib/auth/join/oracle"
"github.com/gravitational/teleport/lib/join/joinutils"
"github.com/gravitational/teleport/lib/join/legacyjoin"
"github.com/gravitational/teleport/lib/join/oraclejoin"
)

// RegisterUsingOracleMethod registers the caller using the Oracle join method and
// returns signed certs to join the cluster.
// RegisterUsingOracleMethod registers the caller using the legacy Oracle join
// method and returns signed certs to join the cluster.
//
// TODO(nklaassen): DELETE IN 20 when removing the legacy join service.
func (a *Server) RegisterUsingOracleMethod(
ctx context.Context,
tokenReq *types.RegisterUsingTokenRequest,
Expand Down Expand Up @@ -142,25 +145,6 @@ func checkHeaders(headers http.Header, challenge string, clock clockwork.Clock)
return nil
}

func checkOracleAllowRules(claims oracle.Claims, token string, allowRules []*types.ProvisionTokenSpecV2Oracle_Rule) error {
for _, rule := range allowRules {
if rule.Tenancy != claims.TenancyID {
continue
}
if len(rule.ParentCompartments) != 0 && !slices.Contains(rule.ParentCompartments, claims.CompartmentID) {
continue
}
if len(rule.Regions) != 0 && !slices.ContainsFunc(rule.Regions, func(region string) bool {
canonicalRegion, _ := oracle.ParseRegion(region)
return canonicalRegion == claims.Region()
}) {
continue
}
return nil
}
return trace.AccessDenied("instance %v did not match any allow rules in token %v", claims.InstanceID, token)
}

func formatHeaderFromMap(m map[string]string) http.Header {
header := make(http.Header, len(m))
for k, v := range m {
Expand Down Expand Up @@ -223,12 +207,8 @@ func (a *Server) checkOracleRequest(
}

// Check allow rules.
tokenV2, ok := provisionToken.(*types.ProvisionTokenV2)
if !ok {
return oracle.Claims{}, trace.BadParameter("oracle join method only supports ProvisionTokenV2")
}
if err := checkOracleAllowRules(claims, provisionToken.GetName(), tokenV2.Spec.Oracle.Allow); err != nil {
return oracle.Claims{}, trace.Wrap(err)
if err := oraclejoin.CheckOracleAllowRules(&claims, provisionToken); err != nil {
return claims, trace.Wrap(err)
}

return claims, nil
Expand Down
Loading
Loading