Jenkins, CSRF und das Crumb

Jenkins bietet seine Dienste neben der Weboberfläche ebenfalls via Rest-Api an. Wer bei Verwendung dieser die Sicherheit erhöhen möchte, kann in den Einstellungen von Jenkins u.a. die CSRF Protection aktivieren um, Cross Site Request Forgery Angriffe zu verhindern. Jenkins akzeptiert nach Aktivierung des Features Rest-Service Aufrufe dann nur, wenn dem Request ein Token im Header beigefügt wird (in der Jenkins Dokumentation wird dies ‘crumb’ genannt). Als quick & dirty Lösung kann man mittels wget, wie in der Dokumentation beschrieben, ein Token generieren. Dieses kann man dann z.B. über Properties in die Anwendung einfließen lassen. Das ist eine schnelle Lösung für erste Tests, hat aber neben sicherheitstechnischen Bedenken den Nachteil, dass das Token an die Session gebunden ist: wird die Anwendung, die die Rest-Aufrufe durchführt in der Testumgebung beispielsweise auf verschiedene Applicationservern parallel getestet und dabei das gleiche Token verwendet, so erhält der zweite Aufrufer einen Status 403 zurück: invalid crumb.

Viel besser ist es vor jedem Rest-Aufruf ein gültiges Token von Jenkins anzufordern - Jenkins bietet hierfür einen eigenen Rest-Service, der wahlweise im json oder xml Format ein Token zurückliefert. Es bietet sich an hierfür eine eigene Funktion zu schreiben, die bei Bedarf den Tokennamen sowie das Token selbst zurückliefert:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Before a jenkins rest service can be called, we need to get a crumb from jenkins. This crumb needs to be added to the http-header when calling a rest service.
*
* @return m - a Map containg the name of the crumb and the value of the crumb as Strings.
* @throws Exception
*/
private Map<String, String> getCrumbFromJenkins() throws Exception
{
HashMap<String, String> m = new HashMap<String, String>();

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + getAuthenticationCode());

HttpEntity<String> request = new HttpEntity<String>(headers);
RestTemplate restTemplate = new RestTemplate();

ResponseEntity<Object> response = restTemplate.exchange("https://dev.meine-domain.de:8090/crumbIssuer/api/json", HttpMethod.GET, request, Object.class);
CrumbJson crumbJson = new Gson().fromJson(response.getBody().toString(), CrumbJson.class);

m.put("name", crumbJson.crumbRequestField);
m.put("value", crumbJson.crumb);

return m;
}

public class CrumbJson {
public String crumb;
public String crumbRequestField;
}