Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mgr/dashboard: securely store remote cluster token #56237

Merged
merged 1 commit into from Mar 22, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/pybind/mgr/dashboard/frontend/package-lock.json

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

1 change: 1 addition & 0 deletions src/pybind/mgr/dashboard/frontend/package.json
Expand Up @@ -72,6 +72,7 @@
"ng-block-ui": "3.0.2",
"ng-click-outside": "7.0.0",
"ng2-charts": "4.1.1",
"ngx-cookie-service": "17.1.0",
"ngx-pipe-function": "1.0.0",
"ngx-toastr": "17.0.2",
"rxjs": "6.6.3",
Expand Down
Expand Up @@ -18,6 +18,7 @@ import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
import { MultiCluster } from '~/app/shared/models/multi-cluster';
import { SummaryService } from '~/app/shared/services/summary.service';
import { Router } from '@angular/router';
import { CookiesService } from '~/app/shared/services/cookie.service';

@Component({
selector: 'cd-multi-cluster-list',
Expand Down Expand Up @@ -48,7 +49,8 @@ export class MultiClusterListComponent {
public actionLabels: ActionLabelsI18n,
private notificationService: NotificationService,
private authStorageService: AuthStorageService,
private modalService: ModalService
private modalService: ModalService,
private cookieService: CookiesService
) {
this.tableActions = [
{
Expand Down Expand Up @@ -189,6 +191,7 @@ export class MultiClusterListComponent {
itemNames: [cluster['cluster_alias'] + ' - ' + cluster['user']],
submitAction: () =>
this.multiClusterService.deleteCluster(cluster['name'], cluster['user']).subscribe(() => {
this.cookieService.deleteToken(`${cluster['name']}-${cluster['user']}`);
this.modalRef.close();
this.notificationService.show(
NotificationType.success,
Expand Down
Expand Up @@ -9,6 +9,7 @@ import { Icons } from '~/app/shared/enum/icons.enum';
import { MultiCluster } from '~/app/shared/models/multi-cluster';
import { Permissions } from '~/app/shared/models/permissions';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { CookiesService } from '~/app/shared/services/cookie.service';
import {
FeatureTogglesMap$,
FeatureTogglesService
Expand Down Expand Up @@ -56,7 +57,8 @@ export class NavigationComponent implements OnInit, OnDestroy {
private featureToggles: FeatureTogglesService,
private telemetryNotificationService: TelemetryNotificationService,
public prometheusAlertService: PrometheusAlertService,
private motdNotificationService: MotdNotificationService
private motdNotificationService: MotdNotificationService,
private cookieService: CookiesService
) {
this.permissions = this.authStorageService.getPermissions();
this.enabledFeature$ = this.featureToggles.get();
Expand Down Expand Up @@ -178,7 +180,12 @@ export class NavigationComponent implements OnInit, OnDestroy {
onClusterSelection(value: object) {
this.multiClusterService.setCluster(value).subscribe(
(resp: any) => {
localStorage.setItem('cluster_api_url', value['url']);
if (value['cluster_alias'] === 'local-cluster') {
localStorage.setItem('cluster_api_url', '');
} else {
localStorage.setItem('current_cluster_name', `${value['name']}-${value['user']}`);
localStorage.setItem('cluster_api_url', value['url']);
}
this.selectedCluster = this.clustersMap.get(`${value['url']}-${value['user']}`) || {};
const clustersConfig = resp['config'];
if (clustersConfig && typeof clustersConfig === 'object') {
Expand All @@ -192,9 +199,10 @@ export class NavigationComponent implements OnInit, OnDestroy {

if (
clusterName === this.selectedCluster['name'] &&
clusterUser === this.selectedCluster['user']
clusterUser === this.selectedCluster['user'] &&
clusterDetails['cluster_alias'] !== 'local-cluster'
) {
localStorage.setItem('token_of_selected_cluster', clusterToken);
this.cookieService.setToken(`${clusterName}-${clusterUser}`, clusterToken);
}
});
});
Expand Down
Expand Up @@ -20,6 +20,7 @@ import { NotificationService } from './notification.service';
import { MultiClusterService } from '../api/multi-cluster.service';
import { SummaryService } from './summary.service';
import { AuthStorageService } from './auth-storage.service';
import { CookiesService } from './cookie.service';

export class CdHttpErrorResponse extends HttpErrorResponse {
preventDefault: Function;
Expand All @@ -37,7 +38,8 @@ export class ApiInterceptorService implements HttpInterceptor {
public notificationService: NotificationService,
private summaryService: SummaryService,
private authStorageService: AuthStorageService,
private multiClusterService: MultiClusterService
private multiClusterService: MultiClusterService,
private cookieService: CookiesService
) {
this.multiClusterService.subscribe((resp: any) => {
const clustersConfig = resp['config'];
Expand Down Expand Up @@ -91,14 +93,13 @@ export class ApiInterceptorService implements HttpInterceptor {
'api/multi-cluster/auth'
];

const token = localStorage.getItem('token_of_selected_cluster');

if (
!currentRoute.includes('login') &&
!ALWAYS_TO_HUB_APIs.includes(request.url) &&
apiUrl &&
!apiUrl.includes(origin)
) {
const token = this.cookieService.getToken(localStorage.getItem('current_cluster_name'));
reqWithVersion = reqWithVersion.clone({
url: `${apiUrl}${reqWithVersion.url}`,
setHeaders: {
Expand Down
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { CookiesService } from './cookie.service';

describe('CookieService', () => {
let service: CookiesService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CookiesService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';

@Injectable({
providedIn: 'root'
})
export class CookiesService {
constructor(private cookieService: CookieService) {}

setToken(name: string, token: string) {
this.cookieService.set(name, token, null, null, null, true, 'Strict');
}

getToken(name: string): string {
return this.cookieService.get(name);
}

deleteToken(name: string) {
this.cookieService.delete(name);
}
}