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
2 changes: 2 additions & 0 deletions api/client/webclient/webconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ type WebConfig struct {
IsTeam bool `json:"isTeam"`
// IsIGSEnabled is true if [Features.IdentityGovernance] = true
IsIGSEnabled bool `json:"isIgsEnabled"`
// IsPolicyEnabled is true if [Features.Policy] = true
IsPolicyEnabled bool `json:"isPolicyEnabled"`
// featureLimits define limits for features.
// Typically used with feature teasers if feature is not enabled for the
// product type eg: Team product contains teasers to upgrade to Enterprise.
Expand Down
609 changes: 308 additions & 301 deletions api/gen/proto/go/usageevents/v1/usageevents.pb.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions api/proto/teleport/usageevents/v1/usageevents.proto
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ enum CTA {
CTA_ACCESS_MONITORING = 9;
CTA_EXTERNAL_AUDIT_STORAGE = 10;
CTA_OKTA_USER_SYNC = 11;
CTA_ENTRA_ID = 12;
}

// UIDiscoverDeployServiceEvent is emitted after the user installs a Teleport Agent.
Expand Down Expand Up @@ -563,6 +564,7 @@ enum IntegrationEnrollKind {
INTEGRATION_ENROLL_KIND_MACHINE_ID_JENKINS = 16;
INTEGRATION_ENROLL_KIND_MACHINE_ID_ANSIBLE = 17;
INTEGRATION_ENROLL_KIND_SERVICENOW = 18;
INTEGRATION_ENROLL_KIND_ENTRA_ID = 19;
}

// IntegrationEnrollMetadata contains common metadata
Expand Down
257 changes: 133 additions & 124 deletions gen/proto/go/prehog/v1alpha/teleport.pb.go

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions gen/proto/ts/prehog/v1alpha/teleport_pb.ts

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

6 changes: 3 additions & 3 deletions lib/integrations/azureoidc/accessgraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ type adSingleSignOn struct {
CurrentSingleSignOnMode singleSignOnMode `json:"currentSingleSignOnMode"`
}

// tagInfoCache is the format for the file produced by CreateTAGCacheFile.
type tagInfoCache struct {
// TAGInfoCache is the format for the file produced by CreateTAGCacheFile.
type TAGInfoCache struct {
AppSsoSettingsCache []*types.PluginEntraIDAppSSOSettings `json:"app_sso_settings_cache"`
}

Expand Down Expand Up @@ -121,7 +121,7 @@ func CreateTAGCacheFile(ctx context.Context) error {
return trace.Wrap(err)
}

cache := &tagInfoCache{}
cache := &TAGInfoCache{}

for _, app := range appResp.GetValue() {
appID := app.GetAppId()
Expand Down
8 changes: 5 additions & 3 deletions lib/integrations/azureoidc/private.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,22 @@ func exchangeToken(ctx context.Context, tenantID string, token msalToken) (strin
// getPrivateAPIToken uses the azure CLI token cache to exchange a refresh token
// for an access token authenticated to the "private" Azure API.
func getPrivateAPIToken(ctx context.Context, tenantID string) (string, error) {
var err error
tokens, err := getRefreshTokens()
if err != nil {
return "", trace.Wrap(err)
}
for _, token := range tokens {
var tokenStr string
slog.DebugContext(ctx, "trying token", "client_id", token.ClientID)
token, err := exchangeToken(ctx, tenantID, token)
tokenStr, err = exchangeToken(ctx, tenantID, token)
if err != nil {
slog.DebugContext(ctx, "error exchanging token", "err", err)
} else {
return token, nil
return tokenStr, nil
}
}
return "", trace.Errorf("no viable token")
return "", trace.Wrap(err, "no viable token")
}

// privateAPIGet invokes GET on the given endpoint of the "private" main.iam.ad.ext.azure.com azure API.
Expand Down
34 changes: 26 additions & 8 deletions lib/integrations/azureoidc/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ package azureoidc

import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"

Expand Down Expand Up @@ -54,6 +57,20 @@ func createGraphClient() (*msgraphsdk.GraphServiceClient, error) {
return msgraphsdk.NewGraphServiceClient(adapter), nil
}

// EnsureAZLogin invokes `az login` and waits for the command to successfully complete.
// In Azure Cloud Shell, this has the effect of retrieving on-behalf-of user credentials
// which we need to read SSO information (see [CreateTAGCacheFile] and ./private.go),
// as well as prompting the user to choose the desired Azure subscription / directory tenant.
func EnsureAZLogin(ctx context.Context) error {
fmt.Println("We will execute `az login` to acquire the necessary permissions and allow you to choose the desired Entra ID tenant.")
fmt.Println("Please follow the instructions below.")
cmd := exec.CommandContext(ctx, "az", "login", "--only-show-errors")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return trace.Wrap(cmd.Run())
}

func getAzureDir() (string, error) {
usr, err := user.Current()
if err != nil {
Expand All @@ -67,7 +84,8 @@ type azureCLIProfile struct {
}

type azureCLISubscription struct {
TenantID string `json:"tenantID"`
TenantID string `json:"tenantID"`
IsDefault bool `json:"isDefault"`
}

// getTenantID infers the Azure tenant ID from the Azure CLI profiles.
Expand All @@ -87,14 +105,14 @@ func getTenantID() (string, error) {

var profile azureCLIProfile
if err := json.Unmarshal(payload, &profile); err != nil {
return "", trace.Wrap(err)
}
if len(profile.Subscriptions) == 0 {
return "", trace.BadParameter("subscription not found")
return "", trace.Wrap(err, "failed to parse Azure profile")
}

// Users are expected to run this in the Azure Cloud Shell,
// where they are by default authenticated to only one subscription.
return profile.Subscriptions[0].TenantID, nil
for _, subscription := range profile.Subscriptions {
if subscription.IsDefault {
return subscription.TenantID, nil
}
}

return "", trace.NotFound("subscription not found")
}
3 changes: 3 additions & 0 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1707,6 +1707,8 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou
// TODO(mcbattirola): remove isTeam when it is no longer used
isTeam := clusterFeatures.GetProductType() == proto.ProductType_PRODUCT_TYPE_TEAM

policy := clusterFeatures.GetPolicy()

webCfg := webclient.WebConfig{
Edition: modules.GetModules().BuildType(),
Auth: authSettings,
Expand All @@ -1723,6 +1725,7 @@ func (h *Handler) getWebConfig(w http.ResponseWriter, r *http.Request, p httprou
HideInaccessibleFeatures: clusterFeatures.GetFeatureHiding(),
CustomTheme: clusterFeatures.GetCustomTheme(),
IsIGSEnabled: clusterFeatures.GetIdentityGovernance(),
IsPolicyEnabled: policy != nil && policy.Enabled,
FeatureLimits: webclient.FeatureLimits{
AccessListCreateLimit: int(clusterFeatures.GetAccessList().GetCreateLimit()),
AccessMonitoringMaxReportRangeLimit: int(clusterFeatures.GetAccessMonitoring().GetMaxReportRangeLimit()),
Expand Down
11 changes: 0 additions & 11 deletions lib/web/integrations_azureoidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,6 @@ func (h *Handler) azureOIDCConfigure(w http.ResponseWriter, r *http.Request, p h
return nil, trace.Wrap(err)
}

integrationName := queryParams.Get("integrationName")
if len(integrationName) == 0 {
return nil, trace.BadParameter("integrationName must be specified")
}
// Ensure the integration name is valid.
_, err = h.GetProxyClient().GetIntegration(ctx, integrationName)
// NotFound error is ignored to prevent disclosure of whether the integration exists in a public/no-auth endpoint.
if err != nil && !trace.IsNotFound(err) {
return nil, trace.Wrap(err)
}

// The script must execute the following command:
argsList := []string{
"integration", "configure", "azure-oidc",
Expand Down
17 changes: 0 additions & 17 deletions lib/web/integrations_azureoidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ func TestAzureOIDCConfigureScript(t *testing.T) {
{
name: "valid",
reqQuery: url.Values{
"integrationName": []string{"myintegration"},
"authConnectorName": []string{"myconnector"},
"accessGraph": []string{"true"},
},
Expand All @@ -72,22 +71,13 @@ func TestAzureOIDCConfigureScript(t *testing.T) {
{
name: "valid without accessGraph",
reqQuery: url.Values{
"integrationName": []string{"myintegration"},
"authConnectorName": []string{"myconnector"},
},
errCheck: require.NoError,
expectedTeleportArgs: "integration configure azure-oidc " +
`--proxy-public-addr=` + proxyAddr + ` ` +
`--auth-connector-name=myconnector`,
},
{
name: "integrationName invalid",
reqQuery: url.Values{
"integrationName": []string{"myintegration;"},
"authConnectorName": []string{"myconnector"},
},
errCheck: isBadParamErrFn,
},
{
name: "authConnectorName invalid",
reqQuery: url.Values{
Expand All @@ -96,13 +86,6 @@ func TestAzureOIDCConfigureScript(t *testing.T) {
},
errCheck: isBadParamErrFn,
},
{
name: "integrationName missing",
reqQuery: url.Values{
"authConnectorName": []string{"myconnector"},
},
errCheck: isBadParamErrFn,
},
{
name: "authConnectorName missing",
reqQuery: url.Values{
Expand Down
2 changes: 2 additions & 0 deletions proto/prehog/v1alpha/teleport.proto
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ enum CTA {
CTA_ACCESS_MONITORING = 9;
CTA_EXTERNAL_AUDIT_STORAGE = 10;
CTA_OKTA_USER_SYNC = 11;
CTA_ENTRA_ID = 12;
}

// a request forwarded to a kube cluster's API server (other than exec and
Expand Down Expand Up @@ -1020,6 +1021,7 @@ enum IntegrationEnrollKind {
INTEGRATION_ENROLL_KIND_MACHINE_ID_AZURE = 20;
INTEGRATION_ENROLL_KIND_MACHINE_ID_SPACELIFT = 21;
INTEGRATION_ENROLL_KIND_MACHINE_ID_KUBERNETES = 22;
INTEGRATION_ENROLL_KIND_ENTRA_ID = 23;
}

// IntegrationEnrollMetadata contains common metadata
Expand Down
6 changes: 6 additions & 0 deletions tool/teleport/common/integration_configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ func onIntegrationConfAzureOIDCCmd(ctx context.Context, params config.Integratio
// Ensure we print output to the user. LogLevel at this point was set to Error.
utils.InitLogger(utils.LoggingForDaemon, slog.LevelInfo)

if err := azureoidc.EnsureAZLogin(ctx); err != nil {
return trace.Wrap(err)
}

fmt.Println("Teleport is setting up the Azure integration. This may take a few minutes.")

appID, tenantID, err := azureoidc.SetupEnterpriseApp(ctx, params.ProxyPublicAddr, params.AuthConnectorName)
if err != nil {
return trace.Wrap(err)
Expand Down
9 changes: 9 additions & 0 deletions web/packages/design/src/assets/images/icons/entra-id.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 14 additions & 1 deletion web/packages/teleport/src/Integrations/IntegrationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import styled from 'styled-components';
import { Link as InternalRouteLink } from 'react-router-dom';

import { Box, Flex, Image } from 'design';
import { AWSIcon } from 'design/SVGIcon';
import { AWSIcon, AzureIcon } from 'design/SVGIcon';
import slackIcon from 'design/assets/images/icons/slack.svg';
import openaiIcon from 'design/assets/images/icons/openai.svg';
import jamfIcon from 'design/assets/images/icons/jamf.svg';
Expand All @@ -34,6 +34,7 @@ import servicenowIcon from 'design/assets/images/icons/servicenow.svg';
import discordIcon from 'design/assets/images/icons/discord.svg';
import emailIcon from 'design/assets/images/icons/email.svg';
import msteamIcon from 'design/assets/images/icons/msteams.svg';
import entraIdIcon from 'design/assets/images/icons/entra-id.svg';
import Table, { Cell } from 'design/DataTable';
import { MenuButton, MenuItem } from 'shared/components/MenuAction';
import { ToolTipInfo } from 'shared/components/ToolTip';
Expand Down Expand Up @@ -325,6 +326,10 @@ const IconCell = ({ item }: { item: IntegrationLike }) => {
formattedText = 'Microsoft Teams';
icon = <IconContainer src={msteamIcon} />;
break;
case 'entra-id':
formattedText = 'Microsoft Entra ID';
icon = <IconContainer src={entraIdIcon} />;
break;
}
} else {
// Default is integration.
Expand All @@ -338,6 +343,14 @@ const IconCell = ({ item }: { item: IntegrationLike }) => {
</SvgIconContainer>
);
break;
case IntegrationKind.AzureOidc:
formattedText = 'Azure OIDC';
icon = (
<SvgIconContainer>
<AzureIcon size={24} />
</SvgIconContainer>
);
break;
}
}

Expand Down
8 changes: 8 additions & 0 deletions web/packages/teleport/src/Integrations/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ export const plugins: Plugin[] = [
statusCode: IntegrationStatusCode.Running,
spec: {},
},
{
resourceType: 'plugin',
name: 'entra-id',
details: '',
kind: 'entra-id',
statusCode: IntegrationStatusCode.Running,
spec: {},
},
];

export const integrations: Integration[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type Props = {
noIcon?: boolean;
event?: CtaEvent;
textLink?: boolean;
url?: string;
[index: string]: any;
};

Expand All @@ -41,6 +42,7 @@ export function ButtonLockedFeature({
noIcon = false,
event,
textLink = false,
url,
...rest
}: Props) {
const ctx = useTeleport();
Expand All @@ -56,7 +58,7 @@ export function ButtonLockedFeature({
return (
<Link
target="blank"
href={getSalesURL(version, cfg.isEnterprise, event)}
href={getSalesURL(version, cfg.isEnterprise, event, url)}
onClick={handleClick}
{...rest}
>
Expand All @@ -69,7 +71,7 @@ export function ButtonLockedFeature({
<ButtonPrimary
as="a"
target="blank"
href={getSalesURL(version, cfg.isEnterprise, event)}
href={getSalesURL(version, cfg.isEnterprise, event, url)}
onClick={handleClick}
py="12px"
width="100%"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ export const ToolTipNoPermBadge: React.FC<PropsWithChildren<Props>> = ({
export enum BadgeTitle {
LackingPermissions = 'Lacking Permissions',
LackingEnterpriseLicense = 'Enterprise Only',
LackingIgs = 'Teleport Identity Only',
}
3 changes: 3 additions & 0 deletions web/packages/teleport/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ const cfg = {
// access list, and access monitoring.
isIgsEnabled: false,

// isPolicyEnabled refers to the Teleport Policy product
isPolicyEnabled: false,

configDir: '$HOME/.config',

baseUrl: window.location.origin,
Expand Down
3 changes: 2 additions & 1 deletion web/packages/teleport/src/services/integrations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ export type PluginKind =
| 'opsgenie'
| 'okta'
| 'servicenow'
| 'jamf';
| 'jamf'
| 'entra-id';

export type PluginOktaSpec = {
// scimBearerToken is the plain text of the bearer token that Okta will use
Expand Down
Loading