diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.ts index 62a5120d9..06ee5f797 100644 --- a/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.ts +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.ts @@ -138,6 +138,11 @@ export interface CreateUserParams extends BaseParams { password: string } +export interface UpdateUserParams extends BaseParams { + login: string + password: string +} + export interface DeactivateUserParams extends BaseParams { login: string anonymize: boolean @@ -286,6 +291,11 @@ export class SonarqubeClientService { await this.http.fetch('users/create', { method: 'POST', query: params }) } + @StartActiveSpan() + async updateUser(params: UpdateUserParams) { + await this.http.fetch('users/update', { method: 'POST', query: params }) + } + @StartActiveSpan() async deactivateUser(params: DeactivateUserParams) { await this.http.fetch('users/deactivate', { method: 'POST', query: params }) diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.spec.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.spec.ts index 629ae7e30..deded2e3d 100644 --- a/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.spec.ts +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.spec.ts @@ -40,6 +40,7 @@ describe('sonarqubeService', () => { addPermissionUser: vi.fn().mockResolvedValue(undefined), searchUsers: vi.fn().mockImplementation(async function* () { yield* makeEmptyUsersResponse().users }), createUser: vi.fn().mockResolvedValue(undefined), + updateUser: vi.fn().mockResolvedValue(undefined), deactivateUser: vi.fn().mockResolvedValue(undefined), revokeUserToken: vi.fn().mockResolvedValue(undefined), searchProject: vi.fn().mockImplementation(async function* () { yield* makeEmptyProjectsResponse().components }), @@ -178,7 +179,7 @@ describe('sonarqubeService', () => { expect(vault.writeSonarqubeUser).not.toHaveBeenCalled() }) - it('should rotate token when user exists but vault secret is missing', async () => { + it('should regenerate password and rotate token when user exists but vault secret is missing', async () => { const project = makeProjectWithDetails({ repositories: [] }) client.generateUserToken.mockResolvedValue(makeUserToken({ login: project.slug })) client.searchUsers.mockImplementation(async function* () { yield makeSonarqubeUser({ login: project.slug }) }) @@ -186,9 +187,9 @@ describe('sonarqubeService', () => { await service.handleUpsert(project) expect(client.createUser).not.toHaveBeenCalled() + expect(client.updateUser).toHaveBeenCalledWith(expect.objectContaining({ login: project.slug, password: expect.any(String) })) expect(client.generateUserToken).toHaveBeenCalledWith(expect.objectContaining({ login: project.slug })) - expect(vault.writeSonarqubeUser).toHaveBeenCalledWith(project.slug, expect.not.objectContaining({ SONAR_PASSWORD: expect.anything() })) - expect(vault.writeSonarqubeUser).toHaveBeenCalledWith(project.slug, expect.objectContaining({ SONAR_USERNAME: project.slug, SONAR_TOKEN: expect.any(String) })) + expect(vault.writeSonarqubeUser).toHaveBeenCalledWith(project.slug, expect.objectContaining({ SONAR_USERNAME: project.slug, SONAR_PASSWORD: expect.any(String), SONAR_TOKEN: expect.any(String) })) }) it('should delete sonarqube projects for removed repositories', async () => { diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts index d4bd52997..6364d5793 100644 --- a/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts @@ -212,9 +212,11 @@ export class SonarqubeService implements OnModuleInit { } else if (existingSecret) { this.logger.verbose(`SonarQube user already exists with vault credentials (login=${project.slug})`) } else { - this.logger.warn(`SonarQube user exists but vault secret is missing, rotating token (login=${project.slug})`) + this.logger.warn(`SonarQube user exists but vault secret is missing, regenerating password and rotating token (login=${project.slug})`) + const password = generateRandomPassword(30) + await this.client.updateUser({ login: project.slug, password }) const token = await this.rotateToken(project.slug) - newSecret = { SONAR_USERNAME: project.slug, SONAR_TOKEN: token } + newSecret = { SONAR_USERNAME: project.slug, SONAR_PASSWORD: password, SONAR_TOKEN: token } } if (newSecret) { diff --git a/apps/server-nestjs/src/modules/vault/vault-client.service.ts b/apps/server-nestjs/src/modules/vault/vault-client.service.ts index 9176d4d6f..1e4c629eb 100644 --- a/apps/server-nestjs/src/modules/vault/vault-client.service.ts +++ b/apps/server-nestjs/src/modules/vault/vault-client.service.ts @@ -72,7 +72,7 @@ export interface VaultIdentityGroupResponse { export interface SonarqubeUserSecret { SONAR_USERNAME: string - SONAR_PASSWORD?: string + SONAR_PASSWORD: string SONAR_TOKEN: string }