diff --git a/.changeset/clever-rats-rush.md b/.changeset/clever-rats-rush.md new file mode 100644 index 0000000000..c34427f8f5 --- /dev/null +++ b/.changeset/clever-rats-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Sensitive internal fields on `BackstageCredentials` objects are now defined as read-only properties in order to minimize risk of leakage. diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts new file mode 100644 index 0000000000..8454aa3ecc --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createCredentialsWithNonePrincipal, + createCredentialsWithServicePrincipal, + createCredentialsWithUserPrincipal, +} from './helpers'; + +describe('credentials', () => { + it('should be created', () => { + expect(createCredentialsWithServicePrincipal('my-service')).toEqual({ + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal: { + type: 'service', + subject: 'my-service', + }, + }); + + expect( + createCredentialsWithUserPrincipal('user:default/mock', 'my-token'), + ).toEqual({ + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal: { + type: 'user', + userEntityRef: 'user:default/mock', + }, + }); + + expect(createCredentialsWithNonePrincipal()).toEqual({ + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal: { + type: 'none', + }, + }); + }); + + it('should not include tokens when serialized', () => { + expect( + JSON.stringify( + createCredentialsWithServicePrincipal('my-service', 'my-token'), + ), + ).not.toMatch(/my-token/); + + expect( + JSON.stringify( + createCredentialsWithUserPrincipal('user:default/mock', 'my-token'), + ), + ).not.toMatch(/my-token/); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.ts index eebe45eb76..dad36670b9 100644 --- a/packages/backend-defaults/src/entrypoints/auth/helpers.ts +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.ts @@ -28,16 +28,23 @@ export function createCredentialsWithServicePrincipal( token?: string, accessRestrictions?: BackstagePrincipalAccessRestrictions, ): InternalBackstageCredentials { - return { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - token, - principal: { - type: 'service', - subject: sub, - accessRestrictions, + return Object.defineProperty( + { + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal: { + type: 'service', + subject: sub, + accessRestrictions, + }, }, - }; + 'token', + { + enumerable: false, + configurable: true, + value: token, + }, + ); } export function createCredentialsWithUserPrincipal( @@ -45,16 +52,23 @@ export function createCredentialsWithUserPrincipal( token: string, expiresAt?: Date, ): InternalBackstageCredentials { - return { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - token, - expiresAt, - principal: { - type: 'user', - userEntityRef: sub, + return Object.defineProperty( + { + $$type: '@backstage/BackstageCredentials', + version: 'v1', + expiresAt, + principal: { + type: 'user', + userEntityRef: sub, + }, }, - }; + 'token', + { + enumerable: false, + configurable: true, + value: token, + }, + ); } export function createCredentialsWithNonePrincipal(): InternalBackstageCredentials {