Skip to content

Commit f02d857

Browse files
Add ssh and token authentication (#346)
Added ssh and token authentication
1 parent 896976e commit f02d857

37 files changed

+1122
-242
lines changed

build.gradle

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,24 @@ dependencies {
195195
implementation "commons-io:commons-io:2.18.0"
196196
implementation "com.thedeanda:lorem:2.2"
197197
implementation "org.eclipse.jgit:org.eclipse.jgit:7.1.0.202411261347-r"
198+
// https://search.maven.org/artifact/org.eclipse.jgit/org.eclipse.jgit
199+
implementation "org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r"
200+
// Note: jgit.htt.server is not compatible with jakarta yet and neither is there a timeline. Hence, we had to add the source files to our repository.
201+
// Once the compatibility is given, we can switch back to the maven dependency.
202+
implementation "org.eclipse.jgit:org.eclipse.jgit.http.server:7.1.0.202411261347-r"
203+
204+
// apache ssh enabled the ssh git operations in LocalVC together with JGit
205+
implementation "org.apache.sshd:sshd-core:2.14.0"
206+
implementation "org.apache.sshd:sshd-git:2.14.0"
207+
implementation "org.apache.sshd:sshd-osgi:2.14.0"
208+
implementation "org.apache.sshd:sshd-sftp:2.14.0"
209+
210+
implementation "org.bouncycastle:bcpkix-jdk18on:1.79"
211+
implementation "org.bouncycastle:bcprov-jdk18on:1.79"
212+
213+
implementation "io.reactivex.rxjava3:rxjava:3.1.8"
214+
implementation 'io.netty:netty-resolver-dns-native-macos:4.1.100.Final:osx-aarch_64'
215+
implementation "org.eclipse.jgit:org.eclipse.jgit:7.1.0.202411261347-r"
198216
implementation "io.reactivex.rxjava3:rxjava:3.1.10"
199217
implementation "io.netty:netty-resolver-dns-native-macos:4.1.116.Final:osx-aarch_64"
200218
implementation "com.opencsv:opencsv:5.9"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package de.tum.cit.aet.artemisModel;
2+
3+
public enum ArtemisAuthMechanism {
4+
ONLINE_IDE,
5+
PASSWORD,
6+
PARTICIPATION_TOKEN,
7+
SSH,
8+
USER_TOKEN,
9+
}

src/main/java/de/tum/cit/aet/domain/ArtemisUser.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@
44
import com.opencsv.bean.CsvBindByName;
55
import de.tum.cit.aet.util.ArtemisServer;
66
import jakarta.persistence.*;
7+
import java.io.ByteArrayOutputStream;
8+
import java.io.FileWriter;
9+
import java.io.IOException;
10+
import java.io.StringWriter;
11+
import java.math.BigInteger;
12+
import java.nio.ByteBuffer;
13+
import java.security.*;
14+
import java.security.interfaces.RSAPublicKey;
715
import java.time.ZonedDateTime;
16+
import java.util.Arrays;
17+
import java.util.Base64;
18+
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
19+
import org.bouncycastle.util.io.pem.PemObject;
20+
import org.bouncycastle.util.io.pem.PemWriter;
21+
import org.springframework.data.util.Pair;
822

923
@Entity
1024
@Table(name = "artemis_user")
@@ -35,6 +49,14 @@ public class ArtemisUser {
3549
@JsonIgnore
3650
private ZonedDateTime tokenExpirationDate;
3751

52+
@Column(name = "public_ssh_key")
53+
@JsonIgnore
54+
private String publicKey;
55+
56+
@Column(name = "private_ssh_key")
57+
@JsonIgnore
58+
private String privateKey;
59+
3860
public Long getId() {
3961
return id;
4062
}
@@ -90,4 +112,25 @@ public ZonedDateTime getTokenExpirationDate() {
90112
public void setTokenExpirationDate(ZonedDateTime tokenExpirationDate) {
91113
this.tokenExpirationDate = tokenExpirationDate;
92114
}
115+
116+
public String getPrivateKey() {
117+
return privateKey;
118+
}
119+
120+
public void setPrivateKey(String privateKey) {
121+
this.privateKey = privateKey;
122+
}
123+
124+
public String getPublicKey() {
125+
return publicKey;
126+
}
127+
128+
public void setPublicKey(String publicKey) {
129+
this.publicKey = publicKey;
130+
}
131+
132+
public void setKeyPair(Pair<String, String> keyPair) {
133+
this.publicKey = keyPair.getFirst();
134+
this.privateKey = keyPair.getSecond();
135+
}
93136
}

src/main/java/de/tum/cit/aet/domain/RequestType.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ public enum RequestType {
1313
REPOSITORY_INFO,
1414
REPOSITORY_FILES,
1515
MISC,
16+
SETUP_SSH_KEYS,
17+
FETCH_PARTICIPATION_VCS_ACCESS_TOKEN,
18+
CLONE_SSH,
19+
CLONE_TOKEN,
20+
CLONE_PASSWORD,
21+
PUSH_SSH,
22+
PUSH_TOKEN,
23+
PUSH_PASSWORD,
1624
}

src/main/java/de/tum/cit/aet/domain/Simulation.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,24 @@ public class Simulation {
4545
@Column(name = "user_range")
4646
private String userRange;
4747

48+
@Deprecated
49+
@JsonIgnore
4850
@Enumerated(EnumType.STRING)
4951
@Column(name = "ide_type", nullable = false)
5052
private IDEType ideType;
5153

54+
@Column(name = "onlineide_percentage", nullable = false)
55+
private double onlineIdePercentage;
56+
57+
@Column(name = "password_percentage", nullable = false)
58+
private double passwordPercentage;
59+
60+
@Column(name = "token_percentage", nullable = false)
61+
private double tokenPercentage;
62+
63+
@Column(name = "ssh_percentage", nullable = false)
64+
private double sshPercentage;
65+
5266
@Column(name = "number_of_commits_and_pushes_from")
5367
private int numberOfCommitsAndPushesFrom;
5468

@@ -215,6 +229,42 @@ public boolean instructorCredentialsProvided() {
215229
return instructorUsername != null && instructorPassword != null;
216230
}
217231

232+
public double getOnlineIdePercentage() {
233+
return onlineIdePercentage;
234+
}
235+
236+
public void setOnlineIdePercentage(double onlineIdePercentage) {
237+
this.onlineIdePercentage = onlineIdePercentage;
238+
}
239+
240+
public double getPasswordPercentage() {
241+
return passwordPercentage;
242+
}
243+
244+
public void setPasswordPercentage(double passwordPercentage) {
245+
this.passwordPercentage = passwordPercentage;
246+
}
247+
248+
public double getTokenPercentage() {
249+
return tokenPercentage;
250+
}
251+
252+
public void setTokenPercentage(double tokenPercentage) {
253+
this.tokenPercentage = tokenPercentage;
254+
}
255+
256+
public double getSshPercentage() {
257+
return sshPercentage;
258+
}
259+
260+
public void setSshPercentage(double sshPercentage) {
261+
this.sshPercentage = sshPercentage;
262+
}
263+
264+
public boolean participationPercentagesSumUpToHundredPercent() {
265+
return (this.onlineIdePercentage + this.passwordPercentage + this.tokenPercentage + this.sshPercentage) == 100.0;
266+
}
267+
218268
public enum Mode {
219269
/**
220270
* We create a temporary course and exam, prepare the exam and delete everything afterwards.

src/main/java/de/tum/cit/aet/service/CiStatusService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,18 +135,22 @@ public CompletableFuture<Void> subscribeToCiStatusViaResults(SimulationRun simul
135135
} catch (InterruptedException e) {
136136
Thread.currentThread().interrupt();
137137
}
138-
log.debug("Updating CI status for simulation run {}", simulationRun.getId());
138+
log.info("Updating CI status for simulation run {}", simulationRun.getId());
139+
139140
submissions = new ArrayList<>();
140141
for (var participation : participations) {
141142
submissions.addAll(admin.getSubmissions(participation.getId()));
142143
}
143144
numberOfQueuedJobs = submissions.size() - getNumberOfResults(submissions);
145+
log.info("Currently queued buildjobs: {}", numberOfQueuedJobs);
146+
144147
status.setQueuedJobs(numberOfQueuedJobs);
145148
status.setTimeInMinutes(status.getTimeInMinutes() + 1);
146149
status.setAvgJobsPerMinute((double) (status.getTotalJobs() - status.getQueuedJobs()) / status.getTimeInMinutes());
147150
status = ciStatusRepository.save(status);
148151
websocketService.sendRunCiUpdate(simulationRun.getId(), status);
149152
} while (numberOfQueuedJobs > 0);
153+
150154
status.setFinished(true);
151155
status = ciStatusRepository.save(status);
152156
websocketService.sendRunCiUpdate(simulationRun.getId(), status);

src/main/java/de/tum/cit/aet/service/artemis/ArtemisUserService.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010
import de.tum.cit.aet.service.dto.ArtemisUserPatternDTO;
1111
import de.tum.cit.aet.util.ArtemisServer;
1212
import de.tum.cit.aet.util.NumberRangeParser;
13+
import de.tum.cit.aet.util.SshUtils;
1314
import de.tum.cit.aet.web.rest.errors.BadRequestAlertException;
1415
import java.io.*;
1516
import java.util.*;
17+
import java.util.concurrent.atomic.AtomicInteger;
18+
import java.util.stream.IntStream;
1619
import org.slf4j.Logger;
1720
import org.slf4j.LoggerFactory;
21+
import org.springframework.data.util.Pair;
1822
import org.springframework.stereotype.Service;
1923
import org.springframework.web.multipart.MultipartFile;
2024

@@ -68,6 +72,21 @@ public List<ArtemisUser> createArtemisUsersByPattern(ArtemisServer server, Artem
6872
);
6973
simulatedArtemisAdmin.login();
7074
}
75+
log.info("Generate SSH keys... this might take some time");
76+
AtomicInteger sshKeyCounter = new AtomicInteger(0);
77+
int totalKeys = pattern.getTo() - pattern.getFrom();
78+
Pair<String, String>[] pregeneratedSSHkeys = new Pair[totalKeys + 1];
79+
80+
IntStream.range(pattern.getFrom(), pattern.getTo() + 1)
81+
.parallel()
82+
.forEach(i -> {
83+
if (sshKeyCounter.get() % 100 == 0) {
84+
log.info("{{}} of {{}} keys created...", sshKeyCounter.get(), totalKeys);
85+
}
86+
pregeneratedSSHkeys[i - pattern.getFrom()] = SshUtils.generateSshKeyPair();
87+
sshKeyCounter.getAndIncrement();
88+
});
89+
log.info("Done generating {{}} SSH keys", totalKeys);
7190

7291
List<ArtemisUser> createdUsers = new ArrayList<>();
7392
for (int i = pattern.getFrom(); i < pattern.getTo(); i++) {
@@ -78,6 +97,8 @@ public List<ArtemisUser> createArtemisUsersByPattern(ArtemisServer server, Artem
7897
var password = pattern.getPasswordPattern().replace("{i}", String.valueOf(i));
7998
artemisUser.setUsername(username);
8099
artemisUser.setPassword(password);
100+
artemisUser.setKeyPair(pregeneratedSSHkeys[i - pattern.getFrom()]);
101+
81102
try {
82103
ArtemisUser createdUser = saveArtemisUser(artemisUser);
83104
// Create user on Artemis if necessary
@@ -111,6 +132,7 @@ public ArtemisUser createArtemisUser(ArtemisServer server, ArtemisUserForCreatio
111132
artemisUser.setServer(server);
112133
artemisUser.setUsername(artemisUserDTO.getUsername());
113134
artemisUser.setPassword(artemisUserDTO.getPassword());
135+
artemisUser.setKeyPair(SshUtils.generateSshKeyPair());
114136

115137
if (artemisUserDTO.getServerWideId() != null) {
116138
artemisUser.setServerWideId(artemisUserDTO.getServerWideId());

src/main/java/de/tum/cit/aet/service/artemis/interaction/SimulatedArtemisAdmin.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,32 @@ public Course createCourse() {
178178
.block();
179179
}
180180

181+
public void cancelAllQueuedBuildJobs() {
182+
if (!authenticated) {
183+
throw new IllegalStateException("User " + username + " is not logged in or does not have the necessary access rights.");
184+
}
185+
186+
webClient
187+
.delete()
188+
.uri(uriBuilder -> uriBuilder.pathSegment("api", "admin", "cancel-all-queued-jobs").build())
189+
.retrieve()
190+
.toBodilessEntity()
191+
.block();
192+
}
193+
194+
public void cancelAllRunningBuildJobs() {
195+
if (!authenticated) {
196+
throw new IllegalStateException("User " + username + " is not logged in or does not have the necessary access rights.");
197+
}
198+
199+
webClient
200+
.delete()
201+
.uri(uriBuilder -> uriBuilder.pathSegment("api", "admin", "cancel-all-running-jobs").build())
202+
.retrieve()
203+
.toBodilessEntity()
204+
.block();
205+
}
206+
181207
/**
182208
* Create an exam for benchmarking.
183209
* @param course the course for which to create the exam

0 commit comments

Comments
 (0)