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
8 changes: 5 additions & 3 deletions src/oc/ocWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import * as tmp from 'tmp';
import validator from 'validator';
import { CommandOption, CommandText } from '../base/command';
import { CliChannel, ExecutionContext } from '../cli';
import { CliExitData } from '../util/childProcessUtil';
import { isOpenShiftCluster, KubeConfigUtils } from '../util/kubeUtils';
import { Project } from './project';
import { ClusterType, KubernetesConsole } from './types';
import { CliExitData } from '../util/childProcessUtil';

/**
* A wrapper around the `oc` CLI tool.
Expand Down Expand Up @@ -612,8 +612,10 @@ export class Oc {
const currentUser = kcu.getCurrentUser();
if (currentUser) {
const projectPrefix = currentUser.name.substring(0, currentUser.name.indexOf('/'));
if (projectPrefix.length > 0) {
activeProject = fixedProjects.find((project) => project.name.includes(projectPrefix));
const matches = projectPrefix.match(/^system:serviceaccount:([a-zA-Z-_.]+-dev):pipeline$/);
const projectName = matches ? matches[1] : projectPrefix;
if (projectName.length > 0) {
activeProject = fixedProjects.find((project) => project.name.includes(projectName));
if (activeProject) {
activeProject.active = true;
void Oc.Instance.setProject(activeProject.name, executionContext);
Expand Down
80 changes: 79 additions & 1 deletion src/openshift/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { KubernetesObject } from '@kubernetes/client-node';
import { CoreV1Api, KubeConfig, KubernetesObject, V1Secret, V1ServiceAccount } from '@kubernetes/client-node';
import { Cluster as KcuCluster, Context as KcuContext } from '@kubernetes/client-node/dist/config_types';
import * as https from 'https';
import { ExtensionContext, QuickInputButtons, QuickPickItem, QuickPickItemButtonEvent, ThemeIcon, Uri, commands, env, window, workspace } from 'vscode';
Expand Down Expand Up @@ -1070,4 +1070,82 @@ export class Cluster extends OpenShiftItem {
const asUrl = new URL(url);
return asUrl.hostname.endsWith('openshiftapps.com');
}

static prepareSSOInKubeConfig(proxy: string, username: string, accessToken: string): KubeConfig {
const kcu = new KubeConfig();
const clusterProxy = {
name: 'sandbox-proxy',
server: proxy,
};
const user = {
name: 'sso-user',
token: accessToken,
};
const context = {
cluster: clusterProxy.name,
name: 'sandbox-proxy-context',
user: user.name,
namespace: `${username}-dev`,
};
kcu.addCluster(clusterProxy);
kcu.addUser(user)
kcu.addContext(context);
kcu.setCurrentContext(context.name);
return kcu;
}

static async installPipelineSecretToken(k8sApi: CoreV1Api, pipelineServiceAccount: V1ServiceAccount, username: string): Promise<V1Secret> {
const v1Secret = {
apiVersion: 'v1',
kind: 'Secret',
metadata: {
name: `pipeline-secret-${username}-dev`,
annotations: {
'kubernetes.io/service-account.name': pipelineServiceAccount.metadata.name,
'kubernetes.io/service-account.uid': pipelineServiceAccount.metadata.uid
}
},
type: 'kubernetes.io/service-account-token'
} as V1Secret

try {
await k8sApi.createNamespacedSecret(`${username}-dev`, v1Secret);
} catch {
// Ignore
}
const newSecrets = await k8sApi.listNamespacedSecret(`${username}-dev`);
return newSecrets?.body.items.find((secret) => secret.metadata.name === `pipeline-secret-${username}-dev`);
}

static async getPipelineServiceAccountToken(k8sApi: CoreV1Api, username: string): Promise<string> {
try {
const serviceAccounts = await k8sApi.listNamespacedServiceAccount(`${username}-dev`);
const pipelineServiceAccount = serviceAccounts.body.items.find(serviceAccount => serviceAccount.metadata.name === 'pipeline');
if (!pipelineServiceAccount) {
return;
}

const secrets = await k8sApi.listNamespacedSecret(`${username}-dev`);
let pipelineTokenSecret = secrets?.body.items.find((secret) => secret.metadata.name === `pipeline-secret-${username}-dev`);
if (!pipelineTokenSecret) {
pipelineTokenSecret = await Cluster.installPipelineSecretToken(k8sApi, pipelineServiceAccount, username);
if (!pipelineTokenSecret) {
return;
}
}
return Buffer.from(pipelineTokenSecret.data.token, 'base64').toString();
} catch {
// Ignore
}
}

static async loginUsingPipelineServiceAccountToken(server: string, proxy: string, username: string, accessToken: string): Promise<string> {
const kcu = Cluster.prepareSSOInKubeConfig(proxy, username, accessToken);
const k8sApi = kcu.makeApiClient(CoreV1Api);
const pipelineToken = await this.getPipelineServiceAccountToken(k8sApi, username);
if (!pipelineToken) {
return;
}
return Cluster.tokenLogin(server, true, pipelineToken);
}
}
1 change: 1 addition & 0 deletions src/openshift/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface SBSignupResponse {
givenName: string;
status: SBStatus;
username: string;
proxyURL: string;
}

export interface SBResponseData {
Expand Down
56 changes: 44 additions & 12 deletions src/webview/cluster/app/sandboxView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ export default function addSandboxView(): JSX.Element {
const [currentState, setCurrentState] = React.useState({
action: 'sandboxPageDetectAuthSession',
statusInfo: '',
usePipelineToken: false,
consoleDashboard: '',
apiEndpoint: '',
apiEndpointProxy: '',
oauthTokenEndpoint: '',
errorCode: undefined
});
Expand Down Expand Up @@ -149,7 +151,9 @@ export default function addSandboxView(): JSX.Element {
action: currentState.action,
consoleDashboard: currentState.consoleDashboard,
statusInfo: currentState.statusInfo,
usePipelineToken: false,
apiEndpoint: '',
apiEndpointProxy: '',
oauthTokenEndpoint: '',
errorCode: undefined
});
Expand Down Expand Up @@ -299,8 +303,10 @@ export default function addSandboxView(): JSX.Element {
setCurrentState({
action: 'sandboxPageRequestVerificationCode',
statusInfo: '',
usePipelineToken: false,
consoleDashboard: '',
apiEndpoint: '',
apiEndpointProxy: '',
oauthTokenEndpoint: '',
errorCode: undefined
});
Expand Down Expand Up @@ -379,11 +385,27 @@ export default function addSandboxView(): JSX.Element {
const Provisioned = () => {

const handleLoginButton = () => {
postMessage('sandboxLoginUsingDataInClipboard', {apiEndpointUrl: currentState.apiEndpoint, oauthRequestTokenUrl: `${currentState.oauthTokenEndpoint}/request`});
if (!currentState.usePipelineToken) { // Try loging in using a token from the Clipboard
postMessage('sandboxLoginUsingDataInClipboard', {
apiEndpointUrl: currentState.apiEndpoint,
oauthRequestTokenUrl: `${currentState.oauthTokenEndpoint}/request`
});
} else { // Try loging in using a Pipeline Token
postMessage('sandboxLoginUsingPipelineToken', {
apiEndpointUrl: currentState.apiEndpoint,
oauthRequestTokenUrl: `${currentState.oauthTokenEndpoint}/request`,
username: currentState.statusInfo,
apiEndpointProxy: currentState.apiEndpointProxy,
});
}
};

const invalidToken = currentState.errorCode === 'invalidToken';
const loginSandboxTitle = !invalidToken ? 'Login to DevSandbox OpenShift cluster with token from clipboard' : 'Token in clipboard is invalid. Select the Get Token option and copy to clipboard';
const loginSandboxTitle = !invalidToken ?
currentState.usePipelineToken ?
'Login to DevSandbox OpenShift cluster using a service account provided token' :
'Login to DevSandbox OpenShift cluster with token from clipboard' :
'Token in clipboard is invalid. Select the Get Token option and copy to clipboard';

return (
<>
Expand All @@ -403,19 +425,29 @@ export default function addSandboxView(): JSX.Element {
</Tooltip>
Your sandbox account has been provisioned and is ready to use.
</Typography>
<Typography variant='caption' color='inherit' display='block' style={{ textAlign:'left', margin: '20px 70px' }}>
Next steps to connect with Developer Sandbox:<br></br>
1. Click on <strong>Get token</strong> button. In the browser, login using <strong>DevSandbox</strong> button.<br></br>
2. Click on <strong>Display token</strong> link and copy token to clipboard.<br></br>
3. Switch back to IDE and press <strong>'Login To DevSandbox'</strong> button. This will login you to DevSandbox with token from clipboard.<br></br>
4. Once successfully logged in, start creating applications and deploy on cluster.
</Typography>
{( !currentState.usePipelineToken ) ? (
<Typography variant='caption' color='inherit' display='block' style={{ textAlign:'left', margin: '20px 70px' }}>
Next steps to connect with Developer Sandbox:<br></br>
1. Click on <strong>Get token</strong> button. In the browser, login using <strong>DevSandbox</strong> button.<br></br>
2. Click on <strong>Display token</strong> link and copy token to clipboard.<br></br>
3. Switch back to IDE and press <strong>'Login To DevSandbox'</strong> button. This will login you to DevSandbox with token from clipboard.<br></br>
4. Once successfully logged in, start creating applications and deploy on cluster.
</Typography>
) : (
<Typography variant='caption' color='inherit' display='block' style={{ textAlign:'left', margin: '20px 70px' }}>
Next steps to connect with Developer Sandbox:<br></br>
1. Press <strong>'Login To DevSandbox'</strong> button. This will login you to DevSandbox using a service account provided token.<br></br>
2. Once successfully logged in, start creating applications and deploy on cluster.
</Typography>
)}
<Tooltip title='Launch your DevSandbox console in browser' placement='bottom'>
<Button variant='contained' className='button' href={currentState.consoleDashboard}>Open Dashboard</Button>
</Tooltip>
<Tooltip title='Open the DevSandbox console page and copy the login token' placement='bottom'>
<Button variant='contained' className='button' href={`${currentState.oauthTokenEndpoint}/request`}>Get token</Button>
</Tooltip>
{( !currentState.usePipelineToken ) && (
<Tooltip title='Open the DevSandbox console page and copy the login token' placement='bottom'>
<Button variant='contained' className='button' href={`${currentState.oauthTokenEndpoint}/request`}>Get token</Button>
</Tooltip>
)}
<Tooltip title={loginSandboxTitle} placement='bottom'>
<div style={{ display: 'inline-block', margin: '8px 0px 8px 0px' }}><Button variant='contained' className='buttonRed' disabled={invalidToken} onClick={handleLoginButton}>Login to DevSandbox</Button></div>
</Tooltip>
Expand Down
Loading