Skip to content

Commit

Permalink
BC-8621 make config init job npm and node free
Browse files Browse the repository at this point in the history
- move sync-indexes (legacy) to management api
- add possibility to encrypt secrets to management api
  • Loading branch information
Loki-Afro authored Jan 3, 2025
1 parent cb1f7a6 commit 3875fff
Show file tree
Hide file tree
Showing 28 changed files with 229 additions and 187 deletions.
21 changes: 21 additions & 0 deletions ansible/roles/schulcloud-server-init/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@
tags:
- configmap

- name: Management Api Configmap File
kubernetes.core.k8s:
kubeconfig: ~/.kube/config
namespace: "{{ NAMESPACE }}"
template: management-configmap.yml.j2
when: WITH_SCHULCLOUD_INIT
tags:
- configmap

- name: Remove Management Api Configmap File
kubernetes.core.k8s:
kubeconfig: ~/.kube/config
namespace: "{{ NAMESPACE }}"
state: absent
api_version: v1
kind: ConfigMap
name: api-management-configmap
when: not WITH_SCHULCLOUD_INIT
tags:
- configmap

- name: Management Deployment
kubernetes.core.k8s:
kubeconfig: ~/.kube/config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,46 @@ metadata:
data:
update.sh: |
#! /bin/bash
# necessary for secret handling and legacy indexes
git clone https://github.com/hpi-schul-cloud/schulcloud-server.git
cd /schulcloud-server
git checkout {{ SCHULCLOUD_SERVER_IMAGE_TAG }}
npm ci
until mongosh $DATABASE__URL --eval "print(\"waited for connection\")"
do
sleep 1
done
mongosh $DATABASE__URL --eval 'rs.initiate({"_id" : "rs0", "members" : [{"_id" : 0, "host" : "localhost:27017"}]})'
sleep 3
if [[ $(mongosh --quiet --eval "db.isMaster().setName") != rs0 ]]
then
echo "replicaset config failed :("
else
echo "gg, hacky mongo replicaset"
fi
echo "seeding database"
curl --retry 360 --retry-all-errors --retry-delay 10 -X POST 'http://mgmt-svc:3333/api/management/database/seed?with-indexes=true'

get_secret() {
local plainText="$1"
local url="http://mgmt-svc:3333/api/management/database/encrypt-plain-text"

if [[ -z "$plainText" ]]; then
echo "Error: No plainText argument provided."
return 1
fi

# Use curl to capture both the response body and status code
local response
local http_code
response=$(curl -s -w "\n%{http_code}" -X POST "$url" \
-H "Content-Type: text/plain" \
--data "$plainText")

# Split the response into body and status code
http_code=$(echo "$response" | tail -n 1)
response_body=$(echo "$response" | head -n -1)

# Check if the response status code is 200
if [[ "$http_code" -ne 200 ]]; then
echo "Error: Request failed with status code $http_code."
return 1
fi

# Return the response body (presumably the secret)
echo "$response_body"
return 0
}
# Below is a series of a MongoDB-data initializations, meant for the development and testing
# purposes on various dev environments - most of them will only work there.

# Test OIDC system configuration used in conjunction with OIDCMOCK deployment.
OIDCMOCK_CLIENT_SECRET=$(node scripts/secret.js -s $AES_KEY -e $OIDCMOCK__CLIENT_SECRET)
OIDCMOCK_CLIENT_SECRET=$(get_secret $OIDCMOCK__CLIENT_SECRET)
# Test LDAP server (deployed in the sc-common namespace) configuration (stored in the 'systems' collection).
SEARCH_USER_PASSWORD=$(node scripts/secret.js -s $LDAP_PASSWORD_ENCRYPTION_KEY -e $SC_COMMON_LDAP_PASSWORD)
SEARCH_USER_PASSWORD=$(get_secret $SC_COMMON_LDAP_PASSWORD)
mongosh $DATABASE__URL --quiet --eval 'db.systems.insertMany([
{
"type" : "ldap",
Expand Down Expand Up @@ -149,7 +162,7 @@ data:
} );'

# Sanis configuration (stored in the 'systems' collection + some related documents in other collections).
SANIS_CLIENT_SECRET=$(node scripts/secret.js -s $AES_KEY -e $SANIS_CLIENT_SECRET)
SANIS_CLIENT_SECRET=$(get_secret $SANIS_CLIENT_SECRET)
SANIS_SYSTEM_ID=0000d186816abba584714c93
if [[ $SC_THEME == "n21" ]]; then
mongosh $DATABASE__URL --quiet --eval 'db.schools.updateMany(
Expand Down Expand Up @@ -223,8 +236,8 @@ data:
ISERV_SYSTEM_ID=0000d186816abba584714c92

# Encrypt secrets that contain IServ's OAuth client secret and LDAP server's search user password.
ISERV_OAUTH_CLIENT_SECRET=$(node scripts/secret.js -s $AES_KEY -e $ISERV_OAUTH_CLIENT_SECRET)
ISERV_LDAP_SEARCH_USER_PASSWORD=$(node scripts/secret.js -s $AES_KEY -e $ISERV_LDAP_SEARCH_USER_PASSWORD)
ISERV_OAUTH_CLIENT_SECRET=$(get_secret $ISERV_OAUTH_CLIENT_SECRET)
ISERV_LDAP_SEARCH_USER_PASSWORD=$(get_secret $ISERV_LDAP_SEARCH_USER_PASSWORD)

# Add (or replace) document with the Dev IServ configuration.
mongosh $DATABASE__URL --quiet --eval 'db.systems.replaceOne(
Expand Down Expand Up @@ -269,7 +282,7 @@ data:
UNIVENTION_LDAP_FEDERAL_STATE_ID=0000b186816abba584714c53

# Encrypt LDAP server's search user password.
UNIVENTION_LDAP_SEARCH_USER_PASSWORD=$(node scripts/secret.js -s $AES_KEY -e $UNIVENTION_LDAP_SEARCH_USER_PASSWORD)
UNIVENTION_LDAP_SEARCH_USER_PASSWORD=$(get_secret $UNIVENTION_LDAP_SEARCH_USER_PASSWORD)

# Add (or replace) document with the test BRB Univention LDAP system configuration.
mongosh $DATABASE__URL --quiet --eval 'db.systems.replaceOne(
Expand Down Expand Up @@ -329,40 +342,6 @@ data:

# Perform the final Bettermarks config data init if client secret and URL has been properly set.
if [ -n "$BETTERMARKS_CLIENT_SECRET" ] && [ -n "$BETTERMARKS_URL" ] && [ -n "$BETTERMARKS_REDIRECT_DOMAIN" ]; then
# Add document to the 'ltitools' collection with Bettermarks tool configuration.
mongosh $DATABASE__URL --quiet --eval 'db.getCollection("ltitools").replaceOne(
{
"name": "bettermarks",
"isTemplate": true
},
{
"roles": [],
"privacy_permission": "anonymous",
"openNewTab": true,
"name": "bettermarks",
"url": "'$BETTERMARKS_URL'",
"key": null,
"secret": "'$BETTERMARKS_CLIENT_SECRET'",
"logo_url": "'$BETTERMARKS_LOGO_URL'",
"oAuthClientId": "'$BETTERMARKS_OAUTH_CLIENT_ID'",
"isLocal": true,
"resource_link_id": null,
"lti_version": null,
"lti_message_type": null,
"isTemplate": true,
"skipConsent": false,
"customs": [],
"createdAt": new Date(),
"updatedAt": new Date(),
"__v": 0,
"isHidden": false,
"frontchannel_logout_uri": null
},
{
"upsert": true
}
);'

# The two steps below (Hydra call and MongoDB insert) were added to automate the actions performed inside
# the server when Bettermarks' OAuth client configuration is added manually in SuperHero Dashboard.

Expand Down Expand Up @@ -423,42 +402,6 @@ data:

# This configures nextcloud in superhero dashboard as oauth2 tool and also in hydra
if [ -n "$NEXTCLOUD_CLIENT_SECRET" ] && [ -n "$NEXTCLOUD_SOCIALLOGIN_OIDC_INTERNAL_NAME" ]; then
echo "Inserting nextcloud to ltitools..."
# Add document to the 'ltitools' collection
mongosh $DATABASE__URL --quiet --eval 'db.getCollection("ltitools").updateOne(
{
"name": "'$NEXTCLOUD_SOCIALLOGIN_OIDC_INTERNAL_NAME'",
"isTemplate": true
},
{ $setOnInsert: {
"roles": [],
"privacy_permission": "anonymous",
"openNewTab": true,
"name": "'$NEXTCLOUD_SOCIALLOGIN_OIDC_INTERNAL_NAME'",
"url": "'$NEXTCLOUD_BASE_URL'",
"key": null,
"secret": "'$NEXTCLOUD_CLIENT_SECRET'",
"logo_url": "",
"oAuthClientId": "'$NEXTCLOUD_CLIENT_ID'",
"isLocal": true,
"resource_link_id": null,
"lti_version": null,
"lti_message_type": null,
"isTemplate": true,
"skipConsent": true,
"customs": [],
"createdAt": new Date(),
"updatedAt": new Date(),
"__v": 0,
"isHidden": true,
"frontchannel_logout_uri": "'$NEXTCLOUD_BASE_URL'apps/schulcloud/logout"
} },
{
"upsert": true
}
);'
echo "Inserted nextcloud to ltitools."

# Add Nextcloud client in hydra
echo "POSTing nextcloud to hydra..."
curl --retry 10 --retry-all-errors --retry-delay 10 \
Expand Down Expand Up @@ -521,7 +464,7 @@ data:

if [ -n "$CTL_SEED_SECRET_ONLINE_DIA_MATHE" ]; then
# Encrypt secrets of external tools that contain an lti11 config.
CTL_SEED_SECRET_ONLINE_DIA_MATHE=$(node scripts/secret.js -s $AES_KEY -e $CTL_SEED_SECRET_ONLINE_DIA_MATHE)
CTL_SEED_SECRET_ONLINE_DIA_MATHE=$(get_secret $CTL_SEED_SECRET_ONLINE_DIA_MATHE)
mongosh $DATABASE__URL --quiet --eval 'db.getCollection("external-tools").updateOne(
{
"name": "Product Test Onlinediagnose Grundschule - Mathematik",
Expand All @@ -536,7 +479,7 @@ data:
fi
if [ -n "$CTL_SEED_SECRET_ONLINE_DIA_DEUTSCH" ]; then
# Encrypt secrets of external tools that contain an lti11 config.
CTL_SEED_SECRET_ONLINE_DIA_DEUTSCH=$(node scripts/secret.js -s $AES_KEY -e $CTL_SEED_SECRET_ONLINE_DIA_DEUTSCH)
CTL_SEED_SECRET_ONLINE_DIA_DEUTSCH=$(get_secret $CTL_SEED_SECRET_ONLINE_DIA_DEUTSCH)
mongosh $DATABASE__URL --quiet --eval 'db.getCollection("external-tools").updateOne(
{
"name": "Product Test Onlinediagnose Grundschule - Deutsch",
Expand All @@ -551,7 +494,7 @@ data:
fi
if [ -n "$CTL_SEED_SECRET_MERLIN" ]; then
# Encrypt secrets of external tools that contain an lti11 config.
CTL_SEED_SECRET_MERLIN=$(node scripts/secret.js -s $AES_KEY -e $CTL_SEED_SECRET_MERLIN)
CTL_SEED_SECRET_MERLIN=$(get_secret $CTL_SEED_SECRET_MERLIN)
mongosh $DATABASE__URL --quiet --eval 'db.getCollection("external-tools").updateOne(
{
"name": "Merlin Bibliothek",
Expand Down Expand Up @@ -583,7 +526,7 @@ data:
if [[ $SC_THEME == "thr" ]]; then
echo "Adding TSP system to systems collection"

TSP_SYSTEM_OAUTH_CLIENT_SECRET=$(node scripts/secret.js -s $AES_KEY -e $TSP_SYSTEM_OAUTH_CLIENT_SECRET)
TSP_SYSTEM_OAUTH_CLIENT_SECRET=$(get_secret $TSP_SYSTEM_OAUTH_CLIENT_SECRET)
mongosh $DATABASE__URL --quiet --eval 'db.systems.insertOne(
{
"_id": ObjectId("66d707f5c5202ba10c5e6256"),
Expand Down Expand Up @@ -611,5 +554,3 @@ data:
fi
# ========== End of TSP system creation

# Database indexes synchronization, it's crucial until we have all the entities in NestJS app.
npm run syncIndexes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: api-management-configmap
namespace: {{ NAMESPACE }}
labels:
app: management-deployment
data:
NEST_LOG_LEVEL: "{{ NEST_LOG_LEVEL }}"
EXIT_ON_ERROR: "true"
ENABLE_SYNC_LEGACY_INDEXES_VIA_FEATHERS_SERVICE: "true"
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ spec:
envFrom:
- configMapRef:
name: api-configmap
- configMapRef:
name: api-management-configmap
- secretRef:
name: api-secret
- secretRef:
Expand Down
21 changes: 16 additions & 5 deletions apps/server/src/apps/management.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import express from 'express';

// register source-map-support for debugging
import { install as sourceMapInstall } from 'source-map-support';

// application imports
import { LegacyLogger } from '@src/core/logger';
import { ManagementServerModule } from '@modules/management';
import { MikroORM } from '@mikro-orm/core';
import legacyAppPromise = require('../../../../src/app');
import { createRequestLoggerMiddleware } from './helpers/request-logger-middleware';
import { enableOpenApiDocs } from './helpers';

Expand All @@ -21,12 +20,24 @@ async function bootstrap() {

const nestExpressAdapter = new ExpressAdapter(nestExpress);
const nestApp = await NestFactory.create(ManagementServerModule, nestExpressAdapter);
const orm = nestApp.get(MikroORM);

nestApp.use(createRequestLoggerMiddleware());

// WinstonLogger
nestApp.useLogger(await nestApp.resolve(LegacyLogger));

// load the legacy feathers/express server
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const feathersExpress = await legacyAppPromise(orm);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
await feathersExpress.setup();

// set reference to legacy app as an express setting so we can
// access it over the current request within FeathersServiceProvider
// TODO remove if not needed anymore, needed for legacy indexes
nestExpress.set('feathersApp', feathersExpress);

// customize nest app settings
nestApp.enableCors();
enableOpenApiDocs(nestApp, 'docs');
Expand All @@ -45,8 +56,8 @@ async function bootstrap() {

console.log('#################################');
console.log(`### Start Management Server ###`);
console.log(`### Port: ${port} ###`);
console.log(`### Base path: ${basePath} ###`);
console.log(`### Port: ${port} ###`);
console.log(`### Base path: ${basePath} ###`);
console.log('#################################');
}
void bootstrap();
4 changes: 2 additions & 2 deletions apps/server/src/infra/encryption/encryption.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { LegacyLogger, LoggerModule } from '@src/core/logger';
import { DefaultEncryptionService, LdapEncryptionService } from './encryption.interface';
import { SymetricKeyEncryptionService } from './encryption.service';
import { SymmetricKeyEncryptionService } from './encryption.service';

function encryptionProviderFactory(configService: ConfigService, logger: LegacyLogger, aesKey: string) {
const key = configService.get<string>(aesKey);
return new SymetricKeyEncryptionService(logger, key);
return new SymmetricKeyEncryptionService(logger, key);
}

@Module({
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/infra/encryption/encryption.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { createMock } from '@golevelup/ts-jest';
import { LegacyLogger } from '@src/core/logger';
import { SymetricKeyEncryptionService } from './encryption.service';
import { SymmetricKeyEncryptionService } from './encryption.service';

describe('SymetricKeyEncryptionService', () => {
describe('with configure encryption key', () => {
const encryptionKey = 'abcdefghijklmnop';
const logger = createMock<LegacyLogger>();
const encryptionService = new SymetricKeyEncryptionService(logger, encryptionKey);
const encryptionService = new SymmetricKeyEncryptionService(logger, encryptionKey);
const testInput = 'testInput';

it('encrypts the input', () => {
Expand All @@ -33,7 +33,7 @@ describe('SymetricKeyEncryptionService', () => {

describe('without configured encryption key', () => {
const logger = createMock<LegacyLogger>();
const encryptionService = new SymetricKeyEncryptionService(logger);
const encryptionService = new SymmetricKeyEncryptionService(logger);
const testInput = 'testInput';

beforeEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/infra/encryption/encryption.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LegacyLogger } from '@src/core/logger';
import { EncryptionService } from './encryption.interface';

@Injectable()
export class SymetricKeyEncryptionService implements EncryptionService {
export class SymmetricKeyEncryptionService implements EncryptionService {
constructor(private logger: LegacyLogger, private key?: string) {
if (!this.key) {
this.logger.warn('No AES key defined. Encryption will no work');
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/infra/feathers/feathers-service.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ export interface FeathersService {
*
* @param id
* @param params
* @deprecated Access legacy eathers service get method
* @deprecated Access legacy feathers service get method
*/
get(id: string, params?: FeathersServiceParams): Promise<FeathersServiceResponse>;
/**
*
* @param params
* @deprecated Access legacy eathers service find method
* @deprecated Access legacy feathers service find method
*/
find(params?: FeathersServiceParams): Promise<FeathersServiceResponse>;
/**
Expand Down
Loading

0 comments on commit 3875fff

Please sign in to comment.