Trust certificates from keystore and specified files

This commit is contained in:
Robin Shen 2019-07-29 11:08:56 +08:00
parent 457995c749
commit ebbe345290
46 changed files with 613 additions and 221 deletions

View File

@ -195,7 +195,7 @@
</repository>
</repositories>
<properties>
<commons.version>1.1.6</commons.version>
<commons.version>1.1.7</commons.version>
<antlr.version>4.7.2</antlr.version>
</properties>
</project>

View File

@ -388,7 +388,7 @@
<dependency>
<groupId>io.onedev</groupId>
<artifactId>k8s-helper</artifactId>
<version>1.0.4</version>
<version>1.0.5</version>
</dependency>
</dependencies>
<properties>

View File

@ -167,7 +167,7 @@ import io.onedev.server.maintenance.RestoreDatabase;
import io.onedev.server.maintenance.Upgrade;
import io.onedev.server.migration.JpaConverter;
import io.onedev.server.migration.PersistentBagConverter;
import io.onedev.server.model.support.DiscoveredJobExecutor;
import io.onedev.server.model.support.AutoDiscoveredJobExecutor;
import io.onedev.server.model.support.authenticator.Authenticator;
import io.onedev.server.notification.CodeCommentNotificationManager;
import io.onedev.server.notification.CommitNotificationManager;
@ -391,12 +391,12 @@ public class CoreModule extends AbstractPluginModule {
@Override
public Class<?> getAbstractClass() {
return DiscoveredJobExecutor.class;
return AutoDiscoveredJobExecutor.class;
}
@Override
public Collection<Class<?>> getImplementations() {
return Sets.newHashSet(DiscoveredJobExecutor.class);
return Sets.newHashSet(AutoDiscoveredJobExecutor.class);
}
});

View File

@ -33,8 +33,8 @@ import io.onedev.server.maintenance.DataManager;
import io.onedev.server.persistence.PersistManager;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.util.ServerConfig;
import io.onedev.server.util.jetty.JettyRunner;
import io.onedev.server.util.serverconfig.ServerConfig;
public class OneDev extends AbstractPlugin implements Serializable {
@ -127,7 +127,7 @@ public class OneDev extends AbstractPlugin implements Serializable {
if (serverConfig.getHttpPort() != 0)
serverUrl = "http://" + hostName + ":" + serverConfig.getHttpPort();
else
serverUrl = "https://" + hostName + ":" + serverConfig.getSslConfig().getPort();
serverUrl = "https://" + hostName + ":" + serverConfig.getHttpsPort();
return StringUtils.stripEnd(serverUrl, "/");
}

View File

@ -44,7 +44,7 @@ import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.security.CodePullAuthorizationSource;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.serverconfig.ServerConfig;
import io.onedev.server.util.ServerConfig;
import io.onedev.server.util.work.WorkExecutor;
@Singleton
@ -128,12 +128,12 @@ public class GitFilter implements Filter {
doNotCache(response);
response.setHeader("Content-Type", "application/x-" + service + "-result");
final Map<String, String> environments = new HashMap<>();
Map<String, String> environments = new HashMap<>();
String serverUrl;
if (serverConfig.getHttpPort() != 0)
serverUrl = "http://localhost:" + serverConfig.getHttpPort();
else
serverUrl = "https://localhost:" + serverConfig.getSslConfig().getPort();
serverUrl = "https://localhost:" + serverConfig.getHttpsPort();
environments.put("ONEDEV_CURL", configManager.getSystemSetting().getCurlConfig().getExecutable());
environments.put("ONEDEV_URL", serverUrl);

View File

@ -38,7 +38,7 @@ import io.onedev.server.event.system.SystemStarting;
import io.onedev.server.model.Setting;
import io.onedev.server.model.Setting.Key;
import io.onedev.server.model.User;
import io.onedev.server.model.support.DiscoveredJobExecutor;
import io.onedev.server.model.support.AutoDiscoveredJobExecutor;
import io.onedev.server.model.support.setting.BackupSetting;
import io.onedev.server.model.support.setting.GlobalIssueSetting;
import io.onedev.server.model.support.setting.MailSetting;
@ -154,7 +154,7 @@ public class DefaultDataManager implements DataManager, Serializable {
}
setting = settingManager.getSetting(Key.JOB_EXECUTORS);
if (setting == null) {
settingManager.saveJobExecutors(Lists.newArrayList(new DiscoveredJobExecutor()));
settingManager.saveJobExecutors(Lists.newArrayList(new AutoDiscoveredJobExecutor()));
}
setting = settingManager.getSetting(Key.MAIL);

View File

@ -110,7 +110,14 @@ public class Upgrade extends DefaultPersistManager {
System.exit(1);
}
if (upgradeDir.list().length != 0) {
boolean isEmpty = true;
for (File file: upgradeDir.listFiles()) {
if (!file.getName().equals("lost+found")) {
isEmpty = false;
break;
}
}
if (!isEmpty) {
if (!new File(upgradeDir, "boot/bootstrap.keys").exists()) {
logger.error("Invalid OneDev installation directory: {}, make sure you are specifying the top level "
+ "installation directory (it contains sub directories such as \"bin\", \"boot\", \"conf\", etc)",

View File

@ -13,7 +13,7 @@ import io.onedev.server.web.editable.EditableUtils;
import io.onedev.server.web.editable.annotation.Editable;
@Editable(order=10000, description="Discover appropriate job executor automatically to run CI jobs")
public class DiscoveredJobExecutor extends JobExecutor {
public class AutoDiscoveredJobExecutor extends JobExecutor {
private static final long serialVersionUID = 1L;

View File

@ -1,4 +1,8 @@
package io.onedev.server.util.serverconfig;
package io.onedev.server.util;
import java.io.File;
import javax.annotation.Nullable;
public interface ServerConfig {
@ -12,15 +16,15 @@ public interface ServerConfig {
*/
int getHttpPort();
/**
* Get ssl config of the server.
* <p>
* @return
* ssl config of the server, or <tt>null</tt> if ssl setting is not defined.
* In case ssl setting is not defined, {@link #getHttpPort()} must not
* return <i>0</i>
*/
SslConfig getSslConfig();
int getHttpsPort();
@Nullable
File getKeystoreFile();
String getKeystorePassword();
@Nullable
File getTrustCertsDir();
/**
* Get web session timeout in seconds.

View File

@ -1,9 +0,0 @@
package io.onedev.server.util.serverconfig;
public interface SslConfig {
int getPort();
String getKeystore();
String getKeystorePassword();
}

View File

@ -6,7 +6,7 @@ import javax.inject.Singleton;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import io.onedev.server.util.serverconfig.ServerConfig;
import io.onedev.server.util.ServerConfig;
@Singleton
public class WebSocketPolicyProvider implements Provider<WebSocketPolicy> {

View File

@ -1,4 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>server-plugin-kubernetes</artifactId>
@ -7,6 +8,13 @@
<artifactId>server-plugin</artifactId>
<version>3.0.1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.62</version>
</dependency>
</dependencies>
<properties>
<moduleClass>io.onedev.server.plugin.kubernetes.KubernetesModule</moduleClass>
</properties>

View File

@ -1,10 +1,21 @@
package io.onedev.server.plugin.kubernetes;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@ -16,6 +27,8 @@ import javax.annotation.Nullable;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,6 +58,7 @@ import io.onedev.server.model.support.JobExecutor;
import io.onedev.server.model.support.RegistryLogin;
import io.onedev.server.plugin.kubernetes.KubernetesExecutor.TestData;
import io.onedev.server.util.JobLogger;
import io.onedev.server.util.ServerConfig;
import io.onedev.server.util.inputspec.SecretInput;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.NameOfEmptyValue;
@ -410,6 +424,72 @@ public class KubernetesExecutor extends JobExecutor implements Testable<TestData
}
}
private String getCertContent(Certificate cert) {
StringWriter stringWriter = new StringWriter();
try (PemWriter pemWriter = new PemWriter(stringWriter)) {
pemWriter.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
pemWriter.flush();
} catch (CertificateEncodingException|IOException e) {
throw new RuntimeException(e);
}
return stringWriter.toString().trim();
}
@Nullable
private String createTrustCertsConfigMap(JobLogger logger) {
Map<String, String> configMapData = new LinkedHashMap<>();
ServerConfig serverConfig = OneDev.getInstance(ServerConfig.class);
File keystoreFile = serverConfig.getKeystoreFile();
if (keystoreFile != null) {
try (InputStream is = new FileInputStream(keystoreFile)) {
KeyStore keystore = KeyStore.getInstance("pkcs12");
keystore.load(is, serverConfig.getKeystorePassword().toCharArray());
Enumeration<String> aliases = keystore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
String siteCertContent = getCertContent(keystore.getCertificate(alias));
String safeAlias = alias.replaceAll("[^a-zA-Z0-9\\.\\_]", "-");
configMapData.put("keystore-site-cert-" + safeAlias + ".pem", siteCertContent);
Certificate chain[] = keystore.getCertificateChain(alias);
if (chain != null) {
for (int i=0; i<chain.length; i++) {
String caCertContent = getCertContent(chain[i]);
if (!caCertContent.equals(siteCertContent))
configMapData.put("keystore-ca-cert-" + safeAlias + "-" + i + ".pem", caCertContent);
}
}
}
} catch (IOException|KeyStoreException|NoSuchAlgorithmException|CertificateException e) {
throw new RuntimeException(e);
}
}
File trustCertsDir = serverConfig.getTrustCertsDir();
if (trustCertsDir != null) {
for (File file: trustCertsDir.listFiles()) {
if (file.isFile()) {
try {
configMapData.put("specified-cert-" + file.getName(), FileUtils.readFileToString(file));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
if (!configMapData.isEmpty()) {
Map<Object, Object> configMapDef = Maps.newLinkedHashMap(
"apiVersion", "v1",
"kind", "ConfigMap",
"metadata", Maps.newLinkedHashMap(
"generateName", "configmap-",
"namespace", "onedev"),
"data", configMapData);
return createResource(configMapDef, new HashSet<>(), logger);
} else {
return null;
}
}
private void execute(String dockerImage, String jobToken, JobLogger logger, @Nullable JobContext jobContext) {
logger.log("Executing job with Kubernetes executor...");
@ -417,10 +497,12 @@ public class KubernetesExecutor extends JobExecutor implements Testable<TestData
String jobSecretName = null;
String imagePullSecretName = null;
String trustCertsConfigMapName = null;
try {
Map<String, String> jobSecrets = Maps.newLinkedHashMap(KubernetesHelper.ENV_JOB_TOKEN, jobToken);
jobSecretName = createSecret(jobSecrets, null, logger);
imagePullSecretName = createImagePullSecret(logger);
trustCertsConfigMapName = createTrustCertsConfigMap(logger);
String osName = getOSName(logger);
@ -433,28 +515,36 @@ public class KubernetesExecutor extends JobExecutor implements Testable<TestData
String k8sHelperClassPath;
String containerCIHome;
String containerCacheHome;
String trustCertsHome;
if (osName.equalsIgnoreCase("linux")) {
containerCIHome = "/onedev-ci";
containerCacheHome = containerCIHome + "/cache";
trustCertsHome = containerCIHome + "/trust-certs";
k8sHelperClassPath = "/k8s-helper/*";
mainContainerSpec.put("command", Lists.newArrayList("sh"));
mainContainerSpec.put("args", Lists.newArrayList(containerCIHome + "/commands.sh"));
} else {
containerCIHome = "C:\\onedev-ci";
containerCacheHome = containerCIHome + "\\cache";
trustCertsHome = containerCIHome + "\\trust-certs";
k8sHelperClassPath = "C:\\k8s-helper\\*";
mainContainerSpec.put("command", Lists.newArrayList("cmd"));
mainContainerSpec.put("args", Lists.newArrayList("/c", containerCIHome + "\\commands.bat"));
}
Map<String, String> ciPathMount = Maps.newLinkedHashMap(
Map<String, String> ciHomeMount = Maps.newLinkedHashMap(
"name", "ci-home",
"mountPath", containerCIHome);
Map<String, String> cacheHomeMount = Maps.newLinkedHashMap(
"name", "cache-home",
"mountPath", containerCacheHome);
Map<String, String> trustCertsMount = Maps.newLinkedHashMap(
"name", "trust-certs-home",
"mountPath", trustCertsHome);
List<Object> volumeMounts = Lists.<Object>newArrayList(ciPathMount, cacheHomeMount);
List<Object> volumeMounts = Lists.<Object>newArrayList(ciHomeMount, cacheHomeMount);
if (trustCertsConfigMapName != null)
volumeMounts.add(trustCertsMount);
mainContainerSpec.put("volumeMounts", volumeMounts);
@ -512,8 +602,15 @@ public class KubernetesExecutor extends JobExecutor implements Testable<TestData
"hostPath", Maps.newLinkedHashMap(
"path", getCacheHome(osName),
"type", "DirectoryOrCreate"));
podSpec.put("volumes", Lists.<Object>newArrayList(ciHomeVolume, cacheHomeVolume));
List<Object> volumes = Lists.<Object>newArrayList(ciHomeVolume, cacheHomeVolume);
if (trustCertsConfigMapName != null) {
Map<Object, Object> trustCertsHomeVolume = Maps.newLinkedHashMap(
"name", "trust-certs-home",
"configMap", Maps.newLinkedHashMap(
"name", trustCertsConfigMapName));
volumes.add(trustCertsHomeVolume);
}
podSpec.put("volumes", volumes);
Map<Object, Object> podDef = Maps.newLinkedHashMap(
"apiVersion", "v1",
@ -648,6 +745,8 @@ public class KubernetesExecutor extends JobExecutor implements Testable<TestData
deleteResource("pod", podName, logger);
}
} finally {
if (trustCertsConfigMapName != null)
deleteResource("configmap", trustCertsConfigMapName, logger);
if (jobSecretName != null)
deleteResource("secret", jobSecretName, logger);
if (imagePullSecretName != null)

View File

@ -0,0 +1,104 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: onedev
labels:
tier: server
spec:
selector:
matchLabels:
tier: server
strategy:
type: Recreate
template:
metadata:
name: onedev
labels:
tier: server
spec:
containers:
- name: onedev
image: 1dev/server
volumeMounts:
- mountPath: "/opt/onedev"
name: onedev
ports:
- containerPort: 6610
env:
- name: hibernate_dialect
value: org.hibernate.dialect.MySQL5InnoDBDialect
- name: hibernate_connection_driver_class
value: com.mysql.cj.jdbc.Driver
- name: hibernate_connection_url
value: jdbc:mysql://mysql:3306/onedev?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false
- name: hibernate_connection_username
value: onedev
- name: hibernate_connection_password
valueFrom:
secretKeyRef:
name: mysql
key: password
initContainers:
- name: init
image: busybox
command: ["sh", "-c", "until nslookup mysql.onedev.svc.cluster.local; do echo waiting for mysql; sleep 2; done;"]
volumes:
- name: onedev
persistentVolumeClaim:
claimName: onedev
serviceAccountName: onedev
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
labels:
tier: database
spec:
selector:
matchLabels:
tier: database
strategy:
type: Recreate
template:
metadata:
name: mysql
labels:
tier: database
spec:
containers:
- name: mysql
image: mysql:5.7
args:
- "--ignore-db-dir=lost+found"
env:
- name: MYSQL_RANDOM_ROOT_PASSWORD
value: "yes"
- name: MYSQL_DATABASE
value: onedev
- name: MYSQL_USER
value: onedev
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql
key: password
ports:
- containerPort: 3306
volumeMounts:
- name: mysql
mountPath: /var/lib/mysql
readinessProbe:
exec:
command:
- bash
- "-c"
- |
mysql -uonedev -p$MYSQL_PASSWORD -e 'SELECT 1'
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
volumes:
- name: mysql
persistentVolumeClaim:
claimName: mysql

View File

@ -0,0 +1,15 @@
namespace: onedev
commonLabels:
app: onedev
secretGenerator:
- name: mysql
literals:
- password=changeit
resources:
- namespaces.yaml
- service-accounts.yaml
- roles.yaml
- role-bindings.yaml
- services.yaml
- deployments.yaml
- persistent-volume-claims.yaml

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: onedev

View File

@ -0,0 +1,19 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: onedev
labels:
tier: server
spec:
accessModes:
- ReadWriteOnce
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql
labels:
tier: database
spec:
accessModes:
- ReadWriteOnce

View File

@ -0,0 +1,23 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: onedev
subjects:
- kind: ServiceAccount
name: onedev
roleRef:
kind: ClusterRole
name: onedev
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: onedev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: onedev
subjects:
- kind: ServiceAccount
name: onedev

View File

@ -0,0 +1,20 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: onedev
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: onedev
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "secrets", "configmaps", "events"]
verbs: ["create", "get", "list", "watch", "delete"]

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: onedev

View File

@ -0,0 +1,27 @@
apiVersion: v1
kind: Service
metadata:
name: onedev
labels:
tier: server
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 6610
protocol: TCP
selector:
tier: server
---
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
tier: database
spec:
ports:
- port: 3306
selector:
tier: database

View File

@ -0,0 +1 @@
This folder configures OneDev with minimum resource requirements to use in resource-constrainted environments such as minikube

View File

@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: onedev
spec:
template:
spec:
containers:
- name: onedev
resources:
requests:
cpu: 500m
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
template:
spec:
containers:
- name: mysql
resources:
requests:
cpu: 250m

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: onedev
spec:
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql
spec:
resources:
requests:
storage: 1Gi

View File

@ -0,0 +1,7 @@
namespace: onedev
bases:
- ../base
patchesStrategicMerge:
- cpu-settings.yaml
- memory-settings.yaml
- disk-settings.yaml

View File

@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: onedev
spec:
template:
spec:
containers:
- name: onedev
resources:
requests:
memory: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
template:
spec:
containers:
- name: mysql
resources:
requests:
memory: 500Mi

View File

@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: onedev
spec:
template:
spec:
containers:
- name: onedev
resources:
requests:
cpu: "1"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
template:
spec:
containers:
- name: mysql
resources:
requests:
cpu: 500m

View File

@ -69,6 +69,8 @@ spec:
containers:
- name: mysql
image: mysql:5.7
args:
- "--ignore-db-dir=lost+found"
env:
- name: MYSQL_RANDOM_ROOT_PASSWORD
value: "yes"

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: onedev
spec:
resources:
requests:
storage: 100Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql
spec:
resources:
requests:
storage: 10Gi

View File

@ -1,17 +1,9 @@
namespace: onedev
bases:
- ../base
commonLabels:
app: onedev
secretGenerator:
- name: mysql
literals:
- password=change-me
resources:
- namespaces.yaml
- service-accounts.yaml
- roles.yaml
- role-bindings.yaml
- services.yaml
- deployments.yaml
- persistent-volume-claims.yaml
patchesStrategicMerge:
- settings.yaml
- cpu-settings.yaml
- memory-settings.yaml
- disk-settings.yaml

View File

@ -9,8 +9,6 @@ spec:
- name: onedev
resources:
requests:
memory: 1Gi
limits:
memory: 4Gi
---
apiVersion: apps/v1
@ -24,24 +22,4 @@ spec:
- name: mysql
resources:
requests:
memory: 512Mi
limits:
memory: 4Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: onedev
spec:
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql
spec:
resources:
requests:
storage: 1Gi
memory: 1Gi

View File

@ -16,5 +16,5 @@ metadata:
name: onedev
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "secrets", "events"]
resources: ["pods", "pods/log", "secrets", "configmaps", "events"]
verbs: ["create", "get", "list", "watch", "delete"]

View File

@ -0,0 +1 @@
This folder demonstrates how to enable SSL support

View File

@ -1,47 +0,0 @@
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

View File

@ -22,11 +22,11 @@ spec:
name: keystore
key: password
volumeMounts:
- name: config
- name: keystore
mountPath: /root/config
ports:
- containerPort: 6643
volumes:
- name: config
- name: keystore
configMap:
name: keystore

View File

@ -4,11 +4,11 @@ bases:
configMapGenerator:
- name: keystore
files:
- keystore=change-me.p12.base64 #PKCS file in base64 encoding
- keystore=changeit.pfx.base64 # base64 encoded pkcs12 keystore
secretGenerator:
- name: keystore
literals:
- password=change-me
- password=changeit
patchesStrategicMerge:
- deployments.yaml
- services.yaml

View File

@ -1,7 +1,7 @@
apiVersion: v1
kind: Service
metadata:
name: onedev #base64
name: onedev
spec:
ports:
- name: https

View File

@ -0,0 +1 @@
This folder demonstrates how to configure OneDev to trust specified certificates

View File

@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: onedev
spec:
template:
metadata:
name: onedev
spec:
containers:
- name: onedev
env:
- name: trust_certs
value: /root/config/trust-certs
volumeMounts:
- name: trust-certs
mountPath: /root/config/trust-certs
volumes:
- name: trust-certs
configMap:
name: trust-certs

View File

@ -0,0 +1,9 @@
namespace: onedev
bases:
- ../production
configMapGenerator:
- name: trust-certs
files:
- onedev.pem
patchesStrategicMerge:
- deployments.yaml

View File

@ -7,13 +7,13 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.internal.guava.Preconditions;
import io.onedev.commons.launcher.bootstrap.Bootstrap;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneException;
import io.onedev.server.util.serverconfig.ServerConfig;
import io.onedev.server.util.serverconfig.SslConfig;
import io.onedev.server.util.ServerConfig;
@Singleton
public class DefaultServerConfig implements ServerConfig {
@ -24,49 +24,54 @@ public class DefaultServerConfig implements ServerConfig {
private static final String PROP_KEYSTORE = "keystore";
private static final String PROP_TRUST_CERTS = "trust_certs";
private static final String PROP_KEYSTORE_ENCODING = "keystore_encoding";
private static final String PROP_KEYSTOREPASSWORD = "keystore_password";
private static final String PROP_SESSION_TIMEOUT = "session_timeout";
private int httpPort;
private int sessionTimeout = 1800;
private int sessionTimeout;
private SslConfig sslConfig;
private int httpsPort;
private File trustCertsDir;
private File keystoreFile;
private String keystorePassword;
@Inject
public DefaultServerConfig(ServerProperties props) {
String httpPortStr = System.getenv(PROP_HTTPPORT);
if (httpPortStr == null)
if (StringUtils.isBlank(httpPortStr))
httpPortStr = props.getProperty(PROP_HTTPPORT);
if (httpPortStr != null)
if (StringUtils.isNotBlank(httpPortStr))
httpPort = Integer.parseInt(httpPortStr.trim());
String httpsPortStr = System.getenv(PROP_HTTPSPORT);
if (httpsPortStr == null)
if (StringUtils.isBlank(httpsPortStr))
httpsPortStr = props.getProperty(PROP_HTTPSPORT);
if (StringUtils.isNotBlank(httpsPortStr))
httpsPort = Integer.parseInt(httpsPortStr.trim());
if (httpsPortStr != null) {
SslConfigBean sslConfigBean = new SslConfigBean();
sslConfigBean.setPort(Integer.parseInt(httpsPortStr.trim()));
String keystorePath = System.getenv(PROP_KEYSTORE);
if (keystorePath == null)
keystorePath = props.getProperty(PROP_KEYSTORE);
if (keystorePath != null)
keystorePath = keystorePath.trim();
else
throw new OneException("Keystore file is required for https support");
String keystorePassword = System.getenv(PROP_KEYSTOREPASSWORD);
if (keystorePassword == null)
keystorePassword = props.getProperty(PROP_KEYSTOREPASSWORD);
if (keystorePassword != null)
keystorePassword = keystorePassword.trim();
File keystoreFile = new File(keystorePath);
if (httpPort == 0 && httpsPort == 0)
throw new RuntimeException("Either " + PROP_HTTPPORT + " or " + PROP_HTTPSPORT + " or both should be enabled");
String keystore = System.getenv(PROP_KEYSTORE);
if (StringUtils.isBlank(keystore))
keystore = props.getProperty(PROP_KEYSTORE);
if (StringUtils.isNotBlank(keystore)) {
keystoreFile = new File(keystore.trim());
if (!keystoreFile.isAbsolute())
keystoreFile = new File(Bootstrap.getConfDir(), keystorePath);
keystoreFile = new File(Bootstrap.getConfDir(), keystore);
Preconditions.checkState(keystoreFile.exists(),
"Keystore file not exist: " + keystoreFile.getAbsolutePath());
String keystoreEncoding = System.getenv(PROP_KEYSTORE_ENCODING);
if (keystoreEncoding == null)
keystoreEncoding = props.getProperty(PROP_KEYSTORE_ENCODING);
@ -75,25 +80,40 @@ public class DefaultServerConfig implements ServerConfig {
if ("base64".equals(keystoreEncoding)) {
try {
String content = FileUtils.readFileToString(keystoreFile);
keystoreFile = File.createTempFile("keystore", "p12");
keystoreFile = File.createTempFile("keystore", "pfx");
FileUtils.writeByteArrayToFile(keystoreFile, Base64.decodeBase64(content));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
sslConfigBean.setKeyStorePath(keystoreFile.getAbsolutePath());
sslConfigBean.setKeyStorePassword(keystorePassword);
sslConfig = sslConfigBean;
} else if (httpsPort != 0) {
throw new OneException(PROP_KEYSTORE + " is required for https support");
}
if (httpPort == 0 && sslConfig == null)
throw new RuntimeException("Either httpPort or httpsPort or both should be enabled.");
keystorePassword = System.getenv(PROP_KEYSTOREPASSWORD);
if (keystorePassword == null)
keystorePassword = props.getProperty(PROP_KEYSTOREPASSWORD);
if (keystorePassword == null)
keystorePassword = "";
String trustCerts = System.getenv(PROP_TRUST_CERTS);
if (StringUtils.isBlank(trustCerts))
trustCerts = props.getProperty(PROP_TRUST_CERTS);
if (StringUtils.isNotBlank(trustCerts)) {
trustCertsDir = new File(trustCerts.trim());
if (!trustCertsDir.isAbsolute())
trustCertsDir = new File(Bootstrap.getConfDir(), trustCerts);
Preconditions.checkState(trustCertsDir.exists(),
"Trust certs directory not exist: " + trustCertsDir.getAbsolutePath());
}
String sessionTimeoutStr = props.getProperty("session_timeout");
String sessionTimeoutStr = System.getenv(PROP_SESSION_TIMEOUT);
if (StringUtils.isBlank(sessionTimeoutStr))
sessionTimeoutStr = props.getProperty(PROP_SESSION_TIMEOUT);
if (StringUtils.isNotBlank(sessionTimeoutStr))
sessionTimeout = Integer.parseInt(sessionTimeoutStr.trim());
else
throw new RuntimeException(PROP_SESSION_TIMEOUT + " should be specified");
}
@Override
@ -101,14 +121,29 @@ public class DefaultServerConfig implements ServerConfig {
return httpPort;
}
@Override
public SslConfig getSslConfig() {
return sslConfig;
}
@Override
public int getSessionTimeout() {
return sessionTimeout;
}
@Override
public int getHttpsPort() {
return httpsPort;
}
@Override
public File getKeystoreFile() {
return keystoreFile;
}
@Override
public String getKeystorePassword() {
return keystorePassword;
}
@Override
public File getTrustCertsDir() {
return trustCertsDir;
}
}

View File

@ -9,9 +9,8 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import io.onedev.server.util.ServerConfig;
import io.onedev.server.util.jetty.ServerConfigurator;
import io.onedev.server.util.serverconfig.ServerConfig;
import io.onedev.server.util.serverconfig.SslConfig;
public class ProductConfigurator implements ServerConfigurator {
@ -31,15 +30,14 @@ public class ProductConfigurator implements ServerConfigurator {
server.addConnector(connector);
}
SslConfig sslConfig = serverConfig.getSslConfig();
if (sslConfig != null) {
if (serverConfig.getHttpsPort() != 0) {
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStoreType("pkcs12");
sslContextFactory.setKeyStorePath(sslConfig.getKeystore());
sslContextFactory.setKeyStorePassword(sslConfig.getKeystorePassword());
sslContextFactory.setKeyStorePath(serverConfig.getKeystoreFile().getAbsolutePath());
sslContextFactory.setKeyStorePassword(serverConfig.getKeystorePassword());
ServerConnector connector = new ServerConnector(server, sslContextFactory);
connector.setPort(sslConfig.getPort());
connector.setPort(serverConfig.getHttpsPort());
HttpConfiguration configuration = new HttpConfiguration();
configuration.addCustomizer(new SecureRequestCustomizer());

View File

@ -9,9 +9,9 @@ import io.onedev.commons.launcher.loader.AbstractPluginModule;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.persistence.HibernateProperties;
import io.onedev.server.util.ServerConfig;
import io.onedev.server.util.jetty.ServerConfigurator;
import io.onedev.server.util.jetty.ServletConfigurator;
import io.onedev.server.util.serverconfig.ServerConfig;
public class ProductModule extends AbstractPluginModule {

View File

@ -21,10 +21,10 @@ import io.onedev.server.git.GitFilter;
import io.onedev.server.git.GitPostReceiveCallback;
import io.onedev.server.git.GitPreReceiveCallback;
import io.onedev.server.security.OneWebEnvironment;
import io.onedev.server.util.ServerConfig;
import io.onedev.server.util.jetty.ClasspathAssetServlet;
import io.onedev.server.util.jetty.FileAssetServlet;
import io.onedev.server.util.jetty.ServletConfigurator;
import io.onedev.server.util.serverconfig.ServerConfig;
import io.onedev.server.web.component.markdown.AttachmentUploadServlet;
import io.onedev.server.web.img.Img;
import io.onedev.server.web.websocket.WebSocketManager;

View File

@ -1,40 +0,0 @@
package io.onedev.server.product;
import io.onedev.server.util.serverconfig.SslConfig;
class SslConfigBean implements SslConfig {
private int port;
private String keyStorePath;
private String keyStorePassword;
@Override
public int getPort() {
return port;
}
@Override
public String getKeystore() {
return keyStorePath;
}
@Override
public String getKeystorePassword() {
return keyStorePassword;
}
public void setPort(int port) {
this.port = port;
}
public void setKeyStorePath(String keyStorePath) {
this.keyStorePath = keyStorePath;
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
}

View File

@ -12,7 +12,7 @@ http_port=6610
#https_port=6643
# path to PKCS12 keystore. Non-absolute path is considered to be relative to OneDev conf directory
#keystore=change-me.p12
#keystore=changeit.pfx
# password of the keystore
#keystore_password=change-me
#keystore_password=changeit