Skip to content

Commit

Permalink
Object Browser: access S3 endpoint via console proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
alfonsomthd committed Jan 24, 2025
1 parent f1d9742 commit 031e436
Show file tree
Hide file tree
Showing 12 changed files with 193 additions and 37 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ yarn test-cypress-headless

By default, it will look for Chrome in the system and use it, but if you want to use Firefox instead, set BRIDGE_E2E_BROWSER_NAME environment variable in your shell with the value firefox.

### NooBaa Object Browser setup

To run NooBaa Object Browser in development mode, do the following:

```
oc port-forward $(oc get pods -n openshift-storage | grep noobaa-endpoint | awk '{print $1}') 6001
CONSOLE_VERSION=4.18 BRIDGE_PLUGIN_PROXY='{"services":[{"consoleAPIPath":"/api/proxy/plugin/odf-console/s3-proxy/","endpoint":"http://localhost:6001"}]}' BRIDGE_PLUGINS='odf-console=http://localhost:9001' PLUGIN=odf yarn dev:c
```

To see the NooBaa S3 logs: `oc logs -f deploy/noobaa-endpoint`

### Debugging with VSCode

To debug with VSCode breakpoints, do the following:
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"webpack-dev-server/express": "^4.21.0"
},
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-sdk/client-s3": "3.667.0",
"@aws-sdk/lib-storage": "3.501.0",
"@aws-sdk/s3-request-presigner": "3.614.0",
Expand All @@ -88,6 +89,8 @@
"@patternfly/react-table": "5.4.8",
"@patternfly/react-tokens": "5.4.1",
"@patternfly/react-topology": "5.4.0",
"@smithy/signature-v4": "5.0.1",
"@smithy/types": "4.1.0",
"@types/lodash-es": "^4.17.4",
"@types/react-dnd-html5-backend": "^3.0.2",
"buffer": "^6.0.3",
Expand Down
11 changes: 7 additions & 4 deletions packages/mco/constants/url-paths.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import {
CONSOLE_PROXY_ROOT_PATH,
MCO_PROXY_ROOT_PATH,
} from '@odf/shared/constants/common';

// ACM thanos proxy endpoint
export const ACM_ENDPOINT =
'/api/proxy/plugin/odf-multicluster-console/acm-thanos-querier';
export const ACM_ENDPOINT = `${MCO_PROXY_ROOT_PATH}/acm-thanos-querier`;
// ACM thanos dev endpoint
export const DEV_ACM_ENDPOINT = '/acm-thanos-querier';
// ACM application details page endpoint
export const applicationDetails =
'/multicloud/applications/details/:namespace/:name';
// ACM search api proxy endpoint
export const ACM_SEARCH_PROXY_ENDPOINT =
'/api/proxy/plugin/acm/console/multicloud/proxy/search';
export const ACM_SEARCH_PROXY_ENDPOINT = `${CONSOLE_PROXY_ROOT_PATH}/acm/console/multicloud/proxy/search`;
// MCO DR navigation item base route
export const DR_BASE_ROUTE = '/multicloud/data-services/disaster-recovery';
37 changes: 16 additions & 21 deletions packages/odf/components/s3-browser/noobaa-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import * as React from 'react';
import { useSafeK8sGet } from '@odf/core/hooks';
import { useODFNamespaceSelector } from '@odf/core/redux';
import { StatusBox } from '@odf/shared/generic/status-box';
import { SecretModel, RouteModel } from '@odf/shared/models';
import { S3Commands } from '@odf/shared/s3';
import { SecretKind, K8sResourceKind } from '@odf/shared/types';
import { SecretModel } from '@odf/shared/models';
import { ODF_S3_PROXY_PATH, S3Commands } from '@odf/shared/s3';
import { SecretKind } from '@odf/shared/types';
import * as _ from 'lodash-es';
import {
NOOBAA_ADMIN_SECRET,
NOOBAA_S3_ROUTE,
NOOBAA_ACCESS_KEY_ID,
NOOBAA_SECRET_ACCESS_KEY,
} from '../../constants';
Expand All @@ -33,48 +32,44 @@ export const NoobaaS3Provider: React.FC<NoobaaS3ProviderType> = ({
loading,
error,
}) => {
const { isODFNsLoaded, odfNsLoadError } = useODFNamespaceSelector();
const { odfNamespace, isODFNsLoaded, odfNsLoadError } =
useODFNamespaceSelector();

const [secretData, secretLoaded, secretError] = useSafeK8sGet<SecretKind>(
SecretModel,
NOOBAA_ADMIN_SECRET
);

const [routeData, routeLoaded, routeError] = useSafeK8sGet<K8sResourceKind>(
RouteModel,
NOOBAA_S3_ROUTE
);

const s3Route = React.useRef<string>();

const [noobaaS3, noobaaS3Error]: [S3Commands, unknown] = React.useMemo(() => {
if (!_.isEmpty(secretData) && !_.isEmpty(routeData)) {
if (!_.isEmpty(secretData)) {
try {
s3Route.current = `https://${routeData.spec.host}`;
s3Route.current = `${window.location.protocol}//${window.location.host}${ODF_S3_PROXY_PATH}`;
const accessKeyId = atob(secretData.data?.[NOOBAA_ACCESS_KEY_ID]);
const secretAccessKey = atob(
secretData.data?.[NOOBAA_SECRET_ACCESS_KEY]
);

return [
new S3Commands(s3Route.current, accessKeyId, secretAccessKey),
new S3Commands(
s3Route.current,
accessKeyId,
secretAccessKey,
odfNamespace
),
null,
];
} catch (err) {
return [{} as S3Commands, err];
}
}
return [{} as S3Commands, null];
}, [secretData, routeData]);
}, [secretData, odfNamespace]);

const allLoaded =
isODFNsLoaded &&
secretLoaded &&
routeLoaded &&
!loading &&
!_.isEmpty(noobaaS3);
const anyError =
odfNsLoadError || secretError || routeError || noobaaS3Error || error;
isODFNsLoaded && secretLoaded && !loading && !_.isEmpty(noobaaS3);
const anyError = odfNsLoadError || secretError || noobaaS3Error || error;

const contextData = React.useMemo(() => {
return { noobaaS3, noobaaS3Route: s3Route.current };
Expand Down
13 changes: 5 additions & 8 deletions packages/odf/components/storage-consumers/onboarding-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from '@odf/core/constants';
import { StorageQuota } from '@odf/core/types';
import { isUnlimitedQuota, isValidQuota } from '@odf/core/utils';
import { FieldLevelHelp, ModalFooter } from '@odf/shared';
import { ODF_PROXY_ROOT_PATH, FieldLevelHelp, ModalFooter } from '@odf/shared';
import { ModalBody, ModalTitle } from '@odf/shared/generic/ModalTitle';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { ExternalLink, getLastLanguage } from '@odf/shared/utils';
Expand Down Expand Up @@ -78,13 +78,10 @@ export const ClientOnBoardingModal: ClientOnBoardingModalProps = ({

const generateToken = () => {
setInProgress(true);
consoleFetch(
'/api/proxy/plugin/odf-console/provider-proxy/onboarding-tokens',
{
method: 'post',
body: quota.value > 0 ? JSON.stringify(quota) : null,
}
)
consoleFetch(`${ODF_PROXY_ROOT_PATH}/provider-proxy/onboarding-tokens`, {
method: 'post',
body: quota.value > 0 ? JSON.stringify(quota) : null,
})
.then((response) => {
setInProgress(false);
if (!response.ok) {
Expand Down
5 changes: 3 additions & 2 deletions packages/odf/constants/attach-storage.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const expandStorageUXBackendEndpoint =
'/api/proxy/plugin/odf-console/provider-proxy/expandstorage';
import { ODF_PROXY_ROOT_PATH } from '@odf/shared/constants/common';

export const expandStorageUXBackendEndpoint = `${ODF_PROXY_ROOT_PATH}/provider-proxy/expandstorage`;
5 changes: 5 additions & 0 deletions packages/shared/src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ export const NOOBA_EXTERNAL_PG_SECRET_NAME = 'noobaa-external-pg';
export const NOOBAA_EXTERNAL_PG_TLS_SECRET_NAME = 'noobaa-external-pg-tls';
export const PLUGIN_VERSION =
typeof process === 'undefined' ? undefined : process?.env?.PLUGIN_VERSION;

// Proxy.
export const CONSOLE_PROXY_ROOT_PATH = '/api/proxy/plugin';
export const ODF_PROXY_ROOT_PATH = `${CONSOLE_PROXY_ROOT_PATH}/odf-console`;
export const MCO_PROXY_ROOT_PATH = `${CONSOLE_PROXY_ROOT_PATH}/odf-multicluster-console`;
4 changes: 3 additions & 1 deletion packages/shared/src/hooks/custom-prometheus-poll/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const ROSA_PROXY_ENDPOINT = '/api/proxy/plugin/odf-console/rosa-prometheus';
import { ODF_PROXY_ROOT_PATH } from '@odf/shared/constants/common';

const ROSA_PROXY_ENDPOINT = `${ODF_PROXY_ROOT_PATH}/rosa-prometheus`;

export const usePrometheusBasePath = () =>
window.SERVER_FLAGS.branding === 'rosa' ? ROSA_PROXY_ENDPOINT : '';
59 changes: 58 additions & 1 deletion packages/shared/src/s3/commands.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Sha256 } from '@aws-crypto/sha256-browser';
import {
S3Client,
ListBucketsCommand,
Expand All @@ -17,6 +18,18 @@ import {
} from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import {
S3_LOCAL_ENDPOINT,
ODF_S3_PROXY_PATH,
S3_INTERNAL_ENDPOINT_PREFIX,
S3_INTERNAL_ENDPOINT_SUFFIX,
} from '@odf/shared/s3/constants';
import { SignatureV4 } from '@smithy/signature-v4';
import {
AwsCredentialIdentity,
HttpRequest,
RequestSigner,
} from '@smithy/types';
import {
CreateBucket,
ListBuckets,
Expand All @@ -35,8 +48,44 @@ import {
GetBucketPolicy,
} from './types';

const s3ClientSigner = (
region: string,
credentials: AwsCredentialIdentity,
odfNamespace: string
): RequestSigner => {
const signer = new SignatureV4({
credentials: credentials,
region: region,
service: 's3',
sha256: Sha256,
});

return {
sign: (request: HttpRequest) => {
// Set the host header & request path that the S3 endpoint will receive
// to calculate signature.
const originalPath = request.path;
request.path = originalPath.replace(ODF_S3_PROXY_PATH, '');
request.headers['host'] = request.headers['host'].includes('localhost')
? S3_LOCAL_ENDPOINT
: `${S3_INTERNAL_ENDPOINT_PREFIX}${odfNamespace}${S3_INTERNAL_ENDPOINT_SUFFIX}`;

return signer.sign(request).then((signedRequest) => {
// Restore the proxy path so the request can reach out the proxy.
signedRequest.path = originalPath;
return signedRequest;
});
},
};
};

export class S3Commands extends S3Client {
constructor(endpoint: string, accessKeyId: string, secretAccessKey: string) {
constructor(
endpoint: string,
accessKeyId: string,
secretAccessKey: string,
odfNamespace: string
) {
super({
// "region" is a required parameter for the SDK, using "none" as a workaround
region: 'none',
Expand All @@ -46,6 +95,14 @@ export class S3Commands extends S3Client {
secretAccessKey,
},
forcePathStyle: true,
signer: s3ClientSigner(
'none',
{
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
},
odfNamespace
),
});
}

Expand Down
6 changes: 6 additions & 0 deletions packages/shared/src/s3/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ODF_PROXY_ROOT_PATH } from '@odf/shared/constants/common';

export const ODF_S3_PROXY_PATH = `${ODF_PROXY_ROOT_PATH}/s3-proxy`;
export const S3_INTERNAL_ENDPOINT_PREFIX = 's3.';
export const S3_INTERNAL_ENDPOINT_SUFFIX = '.svc.cluster.local:443';
export const S3_LOCAL_ENDPOINT = 'localhost:6001';
1 change: 1 addition & 0 deletions packages/shared/src/s3/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './commands';
export * from './constants';
export * from './types';
Loading

0 comments on commit 031e436

Please sign in to comment.