Hi Xavier,
I found something interesting about Rundeck API design, I tried this basic code on Python 3:
import requests s = requests.Session() r = s.post("http://localhost:4440/j_security_check", data={"j_username": "admin", "j_password": "admin1"}) r.status_code r.url print ("####### j_security_check endpoint:") print(r) print ("") r = s.get("http://localhost:4440/api/50/projects",headers = {'Accept': 'application/json'}) print ("####### listing projects endpoint:") print(r)As you see this creates a session based on the j_security_check data and then with this session tries to execute actions against the Rundeck API.
With the correct credentials:
####### j_security_check endpoint: <Response [200]> ####### listing projects endpoint: <Response [200]>With the incorrect credentials:
####### j_security_check endpoint: <Response [200]> ####### listing projects endpoint: <Response [403]>So, the first endpoint always is listening while the instance is running and returns 200 even with the wrong credentials, that is explained here. But with the session created, at the moment of requesting a specific action, the action is rejected because the credentials stored in the session are wrong.
So, if you call any “real action” against the API using the j_security_check method via CURL, you will get the expected responses. I tried in this “too-basic” form (against the system info endpoint):
curl -v -c cookie -b cookie -d j_username=admin -d j_password=admin http://localhost:4440/j_security_check -H "Accept: application/json" http://localhost:4440/api/50/system/info/With the correct user/password:
* WARNING: failed to open cookie file "cookie" * Host localhost:4440 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying 127.0.0.1:4440... * Connected to localhost (127.0.0.1) port 4440 * using HTTP/1.x > POST /j_security_check HTTP/1.1 > Host: localhost:4440 > User-Agent: curl/8.11.0 > Accept: application/json > Content-Length: 33 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 33 bytes < HTTP/1.1 302 Found < Date: Tue, 03 Dec 2024 20:00:55 GMT < Vary: Origin < Vary: Access-Control-Request-Method < Vary: Access-Control-Request-Headers * Added cookie JSESSIONID="node0cavqruhrkpqu1gyfzhgbifij022.node0" for domain localhost, path /, expire 0 < Set-Cookie: JSESSIONID=node0cavqruhrkpqu1gyfzhgbifij022.node0; Path=/; HttpOnly; SameSite=Lax < Expires: Thu, 01 Jan 1970 00:00:00 GMT < Location: http://localhost:4440/ < Content-Length: 0 < * Connection #0 to host localhost left intact * Re-using existing connection with host localhost > POST /api/50/system/info/ HTTP/1.1 > Host: localhost:4440 > User-Agent: curl/8.11.0 > Cookie: JSESSIONID=node0cavqruhrkpqu1gyfzhgbifij022.node0 > Accept: application/json > Content-Length: 33 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 33 bytes < HTTP/1.1 200 OK < Date: Tue, 03 Dec 2024 20:00:55 GMT < Vary: Origin < Vary: Access-Control-Request-Method < Vary: Access-Control-Request-Headers < Referrer-Policy: strict-origin-when-cross-origin < Cache-Control: no-cache, no-store, max-age=0, must-revalidate < Pragma: no-cache < Expires: Thu, 01 Jan 1970 00:00:00 GMT < X-Frame-Options: deny < Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=() < X-XSS-Protection: 0 < X-Content-Type-Options: nosniff < Content-Security-Policy: default-src 'none' ; script-src 'self' https://content.analytics.rundeck.com 'unsafe-inline' 'unsafe-eval' ; style-src 'self' 'unsafe-inline' ; img-src * data: ; font-src 'self' data: ; connect-src 'self' https://api.rundeck.com https://data.analytics.rundeck.com ; form-action 'self' ; < Strict-Transport-Security: max-age=31536000; includeSubDomains * Replaced cookie JSESSIONID="node0vdg2yblph3b31d7am6e1c38ku23.node0" for domain localhost, path /, expire 0 < Set-Cookie: JSESSIONID=node0vdg2yblph3b31d7am6e1c38ku23.node0; Path=/; HttpOnly; SameSite=Lax < Content-Type: application/json;charset=utf-8 < Transfer-Encoding: chunked < {"system":{"executions":{"active":"true","executionMode":"active"},"extended":null,"healthcheck":{"contentType":"application/json","href":"http://localhost:4440/api/50/metrics/healthcheck"},"jvm":{"implementationVersion":"11.0.25+9","name":"OpenJDK 64-Bit Server VM","vendor":"AlienBOB Slackware","version":"11.0.25"},"metrics":{"contentType":"application/json","href":"http://localhost:4440/api/50/metrics/metrics?pretty=true"},"os":{"arch":"amd64","name":"Linux","version":"6.11.10"},"ping":{"contentType":"text/plain","href":"http://localhost:4440/api/50/metrics/ping"},"rundeck":{"apiversion":"50","base":"/home/reideianto/Programs/rundeck/5.7.0","build":"5.7.0-20241021","buildGit":"v5.7.0-0-g1763aa0","node":"localhost","serverUUID":"0a4a4f42-3d40-49c5-8c59-b1272b250905","version":"5.7.0-20241021"},"stats":{"cpu":{"loadAverage":{"average":0,"unit":"percent"},"processors":16},"memory":{"free":602334208,"max":4294967296,"total":1110441984,"unit":"byte"},"scheduler":{"running":0,"threadPoolSize":10},"threads":{"acti* Connection #0 to host localhost left intact ve":41},"uptime":{"duration":2798720,"since":{"datetime":"2024-12-03T19:14:16Z","epoch":1733253256691,"unit":"ms"},"unit":"ms"}},"threadDump":{"contentType":"text/plain","href":"http://localhost:4440/api/50/metrics/threads"},"timestamp":{"datetime":"2024-12-03T20:00:55Z","epoch":1733256055411,"unit":"ms"}}}Returns 200 as expected. Now, with the incorrect credentials:
* Host localhost:4440 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying 127.0.0.1:4440... * Connected to localhost (127.0.0.1) port 4440 * using HTTP/1.x > POST /j_security_check HTTP/1.1 > Host: localhost:4440 > User-Agent: curl/8.11.0 > Cookie: JSESSIONID=node01592f3nz3mnie1o5jkzm1w134y14.node0 > Accept: application/json > Content-Length: 34 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 34 bytes < HTTP/1.1 302 Found < Date: Tue, 03 Dec 2024 19:57:16 GMT < Vary: Origin < Vary: Access-Control-Request-Method < Vary: Access-Control-Request-Headers * Added cookie grails_remember_me="" for domain localhost, path /, expire 1 < Set-Cookie: grails_remember_me=; Path=/; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0 < Expires: Thu, 01 Jan 1970 00:00:00 GMT < Location: http://localhost:4440/user/error < Content-Length: 0 < * Connection #0 to host localhost left intact * Re-using existing connection with host localhost > POST /api/50/system/info/ HTTP/1.1 > Host: localhost:4440 > User-Agent: curl/8.11.0 > Cookie: JSESSIONID=node01592f3nz3mnie1o5jkzm1w134y14.node0 > Accept: application/json > Content-Length: 34 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 34 bytes < HTTP/1.1 403 Forbidden < Date: Tue, 03 Dec 2024 19:57:16 GMT < Vary: Origin < Vary: Access-Control-Request-Method < Vary: Access-Control-Request-Headers < Referrer-Policy: strict-origin-when-cross-origin < Cache-Control: no-cache, no-store, max-age=0, must-revalidate < Pragma: no-cache < Expires: 0 < X-Frame-Options: deny < Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=() < X-XSS-Protection: 0 < X-Content-Type-Options: nosniff < Content-Security-Policy: default-src 'none' ; script-src 'self' https://content.analytics.rundeck.com 'unsafe-inline' 'unsafe-eval' ; style-src 'self' 'unsafe-inline' ; img-src * data: ; font-src 'self' data: ; connect-src 'self' https://api.rundeck.com https://data.analytics.rundeck.com ; form-action 'self' ; < Strict-Transport-Security: max-age=31536000; includeSubDomains < Content-Type: application/json;charset=utf-8 < Content-Length: 131 < * Connection #0 to host localhost left intact {"error":true,"apiversion":50,"errorCode":"unauthorized","message":"(unauthenticated) is not authorized for: /api/50/system/info/"}Returns 403 as expected.
Hope it helps!