Skip to content

Commit 30237ab

Browse files
authored
Merge pull request #209 from cloudogu/feature/cluster-bind-address-on-demand
Minor fixes for least privilege environments
2 parents af5d6b6 + 8f91192 commit 30237ab

File tree

14 files changed

+306
-47
lines changed

14 files changed

+306
-47
lines changed

Dockerfile

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ COPY src/main /app/src/main
2727
COPY compiler.groovy /app
2828

2929
WORKDIR /app
30+
# Exclude code not needed in productive image
31+
RUN cd /app/src/main/groovy/com/cloudogu/gitops/cli/ \
32+
&& rm GenerateJsonSchema.groovy \
33+
&& rm GitopsPlaygroundCliMainScripted.groovy
3034
# Build native image without micronaut
3135
RUN ./mvnw package -DskipTests
3236
# Use simple name for largest jar file -> Easier reuse in later stages
@@ -50,7 +54,7 @@ RUN apk add --no-cache \
5054
gnupg \
5155
outils-sha256 \
5256
git \
53-
bash curl unzip
57+
bash curl unzip zip
5458

5559
RUN mkdir -p /dist/usr/local/bin
5660
RUN mkdir -p /dist/home/.config
@@ -67,7 +71,7 @@ RUN tar -xf helm.tar.gz
6771
RUN echo "${HELM_CHECKSUM} helm.tar.gz" | sha256sum -c
6872
RUN set -o pipefail && curl --location --fail --retry 20 --retry-connrefused --retry-all-errors \
6973
https://raw.githubusercontent.com/helm/helm/main/KEYS | gpg --import --batch --no-default-keyring --keyring /tmp/keyring.gpg
70-
RUN gpgv --keyring /tmp/keyring.gpg helm.tar.gz.asc helm.tar.gz
74+
RUN gpgv --keyring /tmp/keyring.gpg helm.tar.gz.asc helm.tar.gz
7175
RUN mv linux-amd64/helm /dist/usr/local/bin
7276
ENV PATH=$PATH:/dist/usr/local/bin
7377

@@ -107,9 +111,19 @@ RUN rm -r /dist/app/.mvn
107111
RUN rm /dist/app/mvnw
108112
RUN rm /dist/app/pom.xml
109113
RUN rm /dist/app/compiler.groovy
114+
RUN rm -r /dist/app/src/test
110115
RUN cd /dist/app/scripts && rm downloadHelmCharts.sh apply-ng.sh
111116
# For dev image
112-
RUN mv /dist/app/src /src-without-graal && rm -r /src-without-graal/main/groovy/com/cloudogu/gitops/graal
117+
RUN mkdir /dist-dev
118+
# Remove uncessary code and allow changing code in dev mode, less secure, but the intention of the dev image
119+
# Execute bit is required to allow listing of dirs to everyone
120+
RUN mv /dist/app/src /dist-dev/src && \
121+
chmod a=rwx -R /dist-dev/src && \
122+
rm -r /dist-dev/src/main/groovy/com/cloudogu/gitops/graal
123+
# Remove compiled GOP code from jar to avoid duplicate in dev image, allowing for scripting
124+
COPY --from=maven-build /app/gitops-playground.jar /dist-dev/gitops-playground.jar
125+
RUN zip -d /dist-dev/gitops-playground.jar 'com/cloudogu/gitops/*'
126+
113127
# Required to prevent Java exceptions resulting from AccessDeniedException by jgit when running arbitrary user
114128
RUN mkdir -p /dist/root/.config/jgit
115129
RUN touch /dist/root/.config/jgit/config
@@ -136,8 +150,13 @@ RUN tar -xvzf x86_64-linux-musl-native.tgz -C ${RESULT_LIB} --strip-components 1
136150
ENV CC=/musl/bin/gcc
137151
RUN curl --location --fail --retry 20 --retry-connrefused -o zlib.tar.gz https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz
138152
RUN curl --location --fail --retry 20 --retry-connrefused -o zlib.tar.gz.asc https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz.asc
139-
RUN gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys 5ED46A6721D365587791E2AA783FCD8E58BCAFBA # [email protected]
140-
RUN gpg --batch --verify zlib.tar.gz.asc zlib.tar.gz
153+
# Use curl instead of "gpg --recv-keys" for more stable builds with retries
154+
# Key of the zlib maintainer: [email protected]
155+
ENV ZLIB_KEY=5ED46A6721D365587791E2AA783FCD8E58BCAFBA
156+
RUN set -o pipefail && curl --silent --show-error --location --fail --retry 20 --retry-connrefused --retry-delay 5 \
157+
"https://keys.openpgp.org/vks/v1/by-fingerprint/${ZLIB_KEY}" | \
158+
gpg --import --batch --no-default-keyring --keyring /tmp/keyring.gpg
159+
RUN gpgv --keyring /tmp/keyring.gpg zlib.tar.gz.asc zlib.tar.gz
141160
RUN mkdir zlib && tar -xvzf zlib.tar.gz -C zlib --strip-components 1 && \
142161
cd zlib && ./configure --static --prefix=/musl && \
143162
make && make install && \
@@ -196,9 +215,10 @@ ENTRYPOINT ["/app/apply-ng"]
196215
FROM eclipse-temurin:${JDK_VERSION}-jre-alpine as dev
197216

198217
# apply-ng.sh is part of the dev image and allows trying changing groovy code inside the image for debugging
199-
COPY scripts/apply-ng.sh /app/scripts/
200-
COPY --from=maven-build /app/gitops-playground.jar /app/
201-
COPY --from=downloader /src-without-graal /app/src
218+
# Allow changing code in dev mode, less secure, but the intention of the dev image
219+
COPY --chmod=777 scripts/apply-ng.sh /app/scripts/
220+
COPY --from=downloader /dist-dev /app
221+
202222
# Allow initialization in final FROM ${ENV} stage
203223
USER 0
204224
# Avoids ERROR org.eclipse.jgit.util.FS - Cannot save config file 'FileBasedConfig[/app/?/.config/jgit/config]'
@@ -211,7 +231,7 @@ ENTRYPOINT [ "java", \
211231
"org.codehaus.groovy.tools.GroovyStarter", \
212232
"--main", "groovy.ui.GroovyMain", \
213233
"--classpath", "/app/src/main/groovy", \
214-
"/app/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMain.groovy" ]
234+
"/app/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy" ]
215235

216236
# Pick final image according to build-arg
217237
FROM ${ENV}

docs/developers.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -746,14 +746,14 @@ docker run --rm --entrypoint java gitops-playground:dev -classpath /app/gitops-p
746746
On `main` branch:
747747
748748
````shell
749-
TAG=0.2.0
749+
TAG=0.5.0
750750
751751
git checkout main
752-
git pull
753-
git tag -s $TAG -m $TAG
754-
git push --follow-tags
752+
[[ $? -eq 0 ]] && git pull
753+
[[ $? -eq 0 ]] && git tag -s $TAG -m $TAG
754+
[[ $? -eq 0 ]] && git push --follow-tags
755755
756-
xdg-open https://ecosystem.cloudogu.com/jenkins/job/cloudogu-github/job/gitops-playground/job/main/build?delay=0sec
756+
[[ $? -eq 0 ]] && xdg-open https://ecosystem.cloudogu.com/jenkins/job/cloudogu-github/job/gitops-playground/job/main/build?delay=0sec
757757
````
758758
759759
For now, please start a Jenkins Build of `main` manually.

scripts/apply-ng.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
#!/usr/bin/env bash
22
set -o errexit -o nounset -o pipefail
33

4+
TRACE=${TRACE:-}
5+
[[ $TRACE == true ]] && set -x;
6+
47
ABSOLUTE_BASEDIR="$(cd "$(dirname $0)" && pwd)"
58
PLAYGROUND_DIR="$(cd ${ABSOLUTE_BASEDIR} && cd .. && pwd)"
9+
# Allow for overriding the folder to jar via env var
10+
export CLASSPATH="${CLASSPATH:-${PLAYGROUND_DIR}/gitops-playground.jar}"
611

712
function apply-ng() {
813
groovy --classpath "$PLAYGROUND_DIR"/src/main/groovy \
9-
"$PLAYGROUND_DIR"/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMain.groovy "$@"
14+
"$PLAYGROUND_DIR"/src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMainScripted.groovy "$@"
1015
}
1116

1217
# Runs groovy files without needing groovy
1318
function groovy() {
1419
# We don't need the groovy "binary" (script) to start, because the gitops-playground.jar already contains groovy-all.
20+
# Note that gitops-playground.jar is passed via env var CLASSPATH
1521

1622
# Set params like startGroovy does (which is called by the "groovy" script)
1723
# See https://github.com/apache/groovy/blob/master/src/bin/startGroovy
1824
java \
19-
-classpath "$PLAYGROUND_DIR"/gitops-playground.jar \
2025
org.codehaus.groovy.tools.GroovyStarter \
2126
--main groovy.ui.GroovyMain \
2227
"$@"

src/main/groovy/com/cloudogu/gitops/Application.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import jakarta.inject.Singleton
88
@Singleton
99
class Application {
1010

11-
private final List<Feature> features
11+
final List<Feature> features
1212

1313
Application(
1414
List<Feature> features

src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCli.groovy

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ class GitopsPlaygroundCli implements Runnable {
229229
}
230230

231231
def config = getConfig(context, false)
232-
context = context.registerSingleton(new Configuration(config))
232+
register(context, new Configuration(config))
233+
233234
K8sClient k8sClient = context.getBean(K8sClient)
234235

235236
if (config['application']['destroy']) {
@@ -247,6 +248,10 @@ class GitopsPlaygroundCli implements Runnable {
247248
}
248249
}
249250

251+
protected void register(ApplicationContext context, Configuration configuration) {
252+
context.registerSingleton(configuration)
253+
}
254+
250255
private void confirmOrExit(String message, Map config) {
251256
if (config['application']['yes']) {
252257
return

src/main/groovy/com/cloudogu/gitops/cli/GitopsPlaygroundCliMain.groovy

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import picocli.CommandLine
77
@Slf4j
88
class GitopsPlaygroundCliMain {
99

10-
Class<?> commandClass = GitopsPlaygroundCli.class
11-
1210
static void main(String[] args) throws Exception {
13-
new GitopsPlaygroundCliMain().exec(args)
11+
new GitopsPlaygroundCliMain().exec(args, GitopsPlaygroundCli.class)
1412
}
1513

16-
@SuppressWarnings('GrMethodMayBeStatic') // Non-static for easier testing
17-
void exec(String[] args) {
14+
@SuppressWarnings('GrMethodMayBeStatic') // Non-static for easier testing and reuse
15+
void exec(String[] args, Class<?> commandClass) {
1816
// log levels can be set via picocli.trace sys env - defaults to 'WARN'
1917
if (args.contains('--trace') || args.contains('-x'))
2018
System.setProperty("picocli.trace", "DEBUG")
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.cloudogu.gitops.cli
2+
3+
import com.cloudogu.gitops.Application
4+
import com.cloudogu.gitops.config.ApplicationConfigurator
5+
import com.cloudogu.gitops.config.ConfigToConfigFileConverter
6+
import com.cloudogu.gitops.config.Configuration
7+
import com.cloudogu.gitops.config.schema.JsonSchemaGenerator
8+
import com.cloudogu.gitops.config.schema.JsonSchemaValidator
9+
import com.cloudogu.gitops.dependencyinjection.HttpClientFactory
10+
import com.cloudogu.gitops.dependencyinjection.JenkinsFactory
11+
import com.cloudogu.gitops.dependencyinjection.RetrofitFactory
12+
import com.cloudogu.gitops.destroy.ArgoCDDestructionHandler
13+
import com.cloudogu.gitops.destroy.Destroyer
14+
import com.cloudogu.gitops.destroy.JenkinsDestructionHandler
15+
import com.cloudogu.gitops.destroy.ScmmDestructionHandler
16+
import com.cloudogu.gitops.features.*
17+
import com.cloudogu.gitops.features.argocd.ArgoCD
18+
import com.cloudogu.gitops.features.deployment.ArgoCdApplicationStrategy
19+
import com.cloudogu.gitops.features.deployment.Deployer
20+
import com.cloudogu.gitops.features.deployment.HelmStrategy
21+
import com.cloudogu.gitops.jenkins.*
22+
import com.cloudogu.gitops.scmm.ScmmRepoProvider
23+
import com.cloudogu.gitops.utils.*
24+
import groovy.util.logging.Slf4j
25+
import io.micronaut.context.ApplicationContext
26+
import jakarta.inject.Provider
27+
/**
28+
* Micronaut's dependency injection relies on statically compiled class files with seems incompatible with groovy
29+
* scripting/interpretation (without prior compilation).
30+
* The purpose of our -dev image is exactly that: allow groovy scripting inside the image, to shorten the dev cycle on
31+
* air-gapped customer envs.
32+
*
33+
* To make this work the dev image gets it's own main() method that explicitly creates instances of the groovy classes.
34+
* Yes, redundant and not beautiful, but not using dependency injection is worse.
35+
*/
36+
@Slf4j
37+
class GitopsPlaygroundCliMainScripted {
38+
39+
static void main(String[] args) throws Exception {
40+
new GitopsPlaygroundCliMain().exec(args, GitopsPlaygroundCliScripted.class)
41+
}
42+
43+
static class GitopsPlaygroundCliScripted extends GitopsPlaygroundCli {
44+
45+
@Override
46+
protected ApplicationContext createApplicationContext() {
47+
ApplicationContext context = super.createApplicationContext()
48+
49+
// Create ApplicationConfigurator to get started
50+
context.registerSingleton(
51+
new ApplicationConfigurator(
52+
new NetworkingUtils(new K8sClient(new CommandExecutor(), new FileSystemUtils(), null), new CommandExecutor()),
53+
new FileSystemUtils(),
54+
new JsonSchemaValidator(new JsonSchemaGenerator())))
55+
context.registerSingleton(new ConfigToConfigFileConverter())
56+
return context
57+
}
58+
59+
@Override
60+
protected void register(ApplicationContext context, Configuration config) {
61+
super.register(context, config)
62+
63+
// After config is set, create all other beans
64+
65+
def fileSystemUtils = new FileSystemUtils()
66+
def executor = new CommandExecutor()
67+
def k8sClient = new K8sClient(executor, fileSystemUtils, new Provider<Configuration>() {
68+
@Override
69+
Configuration get() {
70+
return config
71+
}
72+
})
73+
def helmClient = new HelmClient(executor)
74+
75+
def httpClientFactory = new HttpClientFactory()
76+
77+
def scmmRepoProvider = new ScmmRepoProvider(config, fileSystemUtils)
78+
def retrofitFactory = new RetrofitFactory()
79+
80+
def insecureSslContextProvider = new Provider<HttpClientFactory.InsecureSslContext>() {
81+
@Override
82+
HttpClientFactory.InsecureSslContext get() {
83+
return httpClientFactory.insecureSslContext()
84+
}
85+
}
86+
def httpClientScmm = retrofitFactory.okHttpClient(httpClientFactory.createLoggingInterceptor(), config, insecureSslContextProvider)
87+
def retrofit = retrofitFactory.retrofit(config, httpClientScmm)
88+
def repoApi = retrofitFactory.repositoryApi(retrofit)
89+
90+
def jenkinsConfiguration = new JenkinsConfigurationAdapter(config)
91+
JenkinsFactory jenkinsFactory = new JenkinsFactory(jenkinsConfiguration)
92+
def jenkinsApiClient = jenkinsFactory.jenkinsApiClient(
93+
httpClientFactory.okHttpClient(httpClientFactory.createLoggingInterceptor(), jenkinsConfiguration, insecureSslContextProvider))
94+
95+
context.registerSingleton(k8sClient)
96+
97+
if (config.config['application']['destroy']) {
98+
context.registerSingleton(new Destroyer([
99+
new ArgoCDDestructionHandler(config, k8sClient, scmmRepoProvider, helmClient, fileSystemUtils),
100+
new ScmmDestructionHandler(config, retrofitFactory.usersApi(retrofit), retrofitFactory.repositoryApi(retrofit)),
101+
new JenkinsDestructionHandler(new JobManager(jenkinsApiClient), config, new GlobalPropertyManager(jenkinsApiClient))
102+
]))
103+
} else {
104+
def helmStrategy = new HelmStrategy(config, helmClient)
105+
106+
def deployer = new Deployer(config, new ArgoCdApplicationStrategy(config, fileSystemUtils, scmmRepoProvider), helmStrategy)
107+
108+
def airGappedUtils = new AirGappedUtils(config, scmmRepoProvider, repoApi, fileSystemUtils, helmClient)
109+
110+
context.registerSingleton(new Application([
111+
new Registry(config, fileSystemUtils, k8sClient, helmStrategy),
112+
new ScmManager(config, executor, fileSystemUtils, helmStrategy),
113+
new Jenkins(config, executor, fileSystemUtils, new GlobalPropertyManager(jenkinsApiClient),
114+
new JobManager(jenkinsApiClient), new UserManager(jenkinsApiClient),
115+
new PrometheusConfigurator(jenkinsApiClient)),
116+
new ArgoCD(config, k8sClient, helmClient, fileSystemUtils, scmmRepoProvider),
117+
new IngressNginx(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
118+
new Mailhog(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
119+
new PrometheusStack(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
120+
new ExternalSecretsOperator(config, fileSystemUtils, deployer, k8sClient, airGappedUtils),
121+
new Vault(config, fileSystemUtils, k8sClient, deployer, airGappedUtils)
122+
]))
123+
}
124+
}
125+
}
126+
}

src/main/groovy/com/cloudogu/gitops/config/ApplicationConfigurator.groovy

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ class ApplicationConfigurator {
116116
password : DEFAULT_ADMIN_PW,
117117
yes : false,
118118
runningInsideK8s : false, // Set dynamically
119-
clusterBindAddress : '', // Set dynamically
120119
namePrefix : '',
121120
podResources : false,
122121
namePrefixForEnvVars : '', // Set dynamically
@@ -348,9 +347,6 @@ class ApplicationConfigurator {
348347
log.debug("installation is running in kubernetes.")
349348
newConfig.application["runningInsideK8s"] = true
350349
}
351-
String clusterBindAddress = networkingUtils.findClusterBindAddress()
352-
log.debug("Setting cluster bind Address: " + clusterBindAddress)
353-
newConfig.application["clusterBindAddress"] = clusterBindAddress
354350
}
355351

356352
private void addScmmConfig(Map newConfig) {
@@ -368,8 +364,8 @@ class ApplicationConfigurator {
368364
} else {
369365
log.debug("Setting internal configs for local single node cluster with internal scmm")
370366
def port = fileSystemUtils.getLineFromFile(fileSystemUtils.getRootDir() + "/scm-manager/values.ftl.yaml", "nodePort:").findAll(/\d+/)*.toString().get(0)
371-
String cba = newConfig.application["clusterBindAddress"]
372-
newConfig.scmm["url"] = networkingUtils.createUrl(cba, port, "/scm")
367+
String clusterBindAddress = networkingUtils.findClusterBindAddress()
368+
newConfig.scmm["url"] = networkingUtils.createUrl(clusterBindAddress, port, "/scm")
373369
}
374370

375371
String scmmUrl = newConfig.scmm["url"]
@@ -396,8 +392,8 @@ class ApplicationConfigurator {
396392
} else {
397393
log.debug("Setting jenkins configs for local single node cluster with internal jenkins")
398394
def port = fileSystemUtils.getLineFromFile(fileSystemUtils.getRootDir() + "/jenkins/values.yaml", "nodePort:").findAll(/\d+/)*.toString().get(0)
399-
String cba = newConfig.application["clusterBindAddress"]
400-
newConfig.jenkins["url"] = networkingUtils.createUrl(cba, port)
395+
String clusterBindAddress = networkingUtils.findClusterBindAddress()
396+
newConfig.jenkins["url"] = networkingUtils.createUrl(clusterBindAddress, port)
401397
}
402398
}
403399

src/main/groovy/com/cloudogu/gitops/config/schema/Schema.groovy

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ class Schema {
140140
@JsonPropertyDescription(PIPE_YES_DESCRIPTION)
141141
boolean yes = false
142142
// boolean runningInsideK8s = ""
143-
// String clusterBindAddress = ""
144143
@JsonPropertyDescription(NAME_PREFIX_DESCRIPTION)
145144
String namePrefix = ""
146145

src/main/groovy/com/cloudogu/gitops/destroy/Destroyer.groovy

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ import jakarta.inject.Singleton
55

66
@Singleton
77
@Slf4j
8-
class Destroyer implements DestructionHandler {
8+
class Destroyer {
99

10-
private final List<DestructionHandler> destructionHandlers
10+
final List<DestructionHandler> destructionHandlers
1111

1212
Destroyer(List<DestructionHandler> destructionHandlers) {
1313
this.destructionHandlers = destructionHandlers
1414
}
1515

16-
@Override
1716
void destroy() {
1817
log.info("Start destroying")
1918
for (def handler in destructionHandlers) {

0 commit comments

Comments
 (0)