Closed qrkourier closed 4 months ago
I verified this works as intended with the following userscript after importing the admin identity's client cert in my web browser and visiting the console binding /zac on the mgmt API URL.
// ==UserScript==
// @name mTLS POST Request Test
// @namespace openziti.io
// @version 2024-06-30
// @description fetch .data.token and use as zt-session header in subsequent requests
// @author @qrkourier
// @match https://ziti.ken.demo.openziti.org:1280/
// @icon https://www.google.com/s2/favicons?sz=64&domain=openziti.org
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Create a POST request
const url = 'https://ziti.ken.demo.openziti.org:1280/edge/management/v1';
fetch(url+'/authenticate?method=cert', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
})
.then(response => {
// Print the full HTTP response
console.log('HTTP Response:', response);
// Check if the response is JSON and print it
return response.json().then(jsonData => {
if (jsonData && jsonData.data && jsonData.data.token) {
// If .data.token is defined and not empty, use it in a subsequent request
const token = jsonData.data.token;
return fetch(url+'/current-identity', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'zt-session': token
},
})
.then(response => {
// Print the full HTTP response
console.log('Subsequent HTTP Response:', response);
return response.json();
})
.then(jsonData => {
console.log('Subsequent Response JSON:', jsonData);
return jsonData;
})
.catch(error => {
console.error('Subsequent Response Error:', error);
});
} else {
console.error('No token found in response');
}
}).catch(error => {
// If not JSON, print the text response
return response.text().then(textData => {
console.log('Response Text:', textData);
return textData;
});
});
})
.catch((error) => {
console.error('Error:', error);
});
})();
Test procedure:
Get an admin identity token
ziti edge create identity kpop4 -A -o /tmp/kpop4.jwt
Enroll to obtain client cert
ziti edge enroll -o /tmp/kpop4.json <(<<< eyJhbGciOiJSUzI1NiIsImtpZCI6IjBlNDNmYWNhNGI1ODNlMTc4N2NjNjYwZDdjNzRkNDk2NmI0OWVkY2QiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3ppdGkua2VuLmRlbW8ub3BlbnppdGkub3JnOjEyODAiLCJzdWIiOiI1dUdyYm5wby0iLCJhdWQiOlsiIl0sImV4cCI6MTcxOTc2MjU1NiwianRpIjoiYjFmZjU4YzktNzU2Zi00YzZhLWI1ZTktZTZhNjlmNmRlN2I3IiwiZW0iOiJvdHQiLCJjdHJscyI6bnVsbH0.i9PeNhjGrHA0WxFJ5_uREXYibOUV5ydLlv3fn5GMHbAs0Svw8--uWrVvVBGSPs0ia8hekE-TXEk6IXywxnD1plwQWkfBBs4probJzRoPmD7S3XkMt5Zp6QUML83X7mkSbCDr97QHR-SdqYZfKQy3RLQiLeyamOF-10WtVsas_xgH0WLJB43FBtBDvcWKw5GTeVRY5KMI0l0AA0IEd9aiv_42YQnPjvKMFcxfqHhNU_DFO9DSqed_6R9yxPOUGXaqWmJVqLYLHZ1wBYhzZuZRzvzxBITl9dxsTJ8Rzd6qoKgX_SWgxnxaodjpEML-h0aLPlQhlUD6s1VymwS7mwqnsM4UH8IQYdBIKucyBvrYTeeKY9uIQ2Loov78rD61m2KpRsBb3cdir9m9rUi44Z9SkZt2O-zEKKjb-0EAoQDVKK9p1LOJvyQVr3Pwxw0Vl3PL7rSI8d6rbsRIDbkzQDM5C9Y_ipOSx2Uy4eFFrZS5QTPbW3O1xLs6SVOv3qduJbDR7suMFX_0hW3YNewK50sfx-SZ5KEUNpmxTDxOojQ1ga3BgGfBuDdtCDR5MOMdJs1vazqYTHubC_H48b-Zy300yjGwMnEtWN_XCD0rPGqX_lPZvOQ4cpfbmM64bhz65smzEK30A5xOldldRuMAzTWsJQ_zYfZWlu1UYRePHeukYKQ)
Unwrap the JSON identity file into separate cert, key, etc.
ziti ops unwrap /tmp/kpop4.json
Fix filemodes because unwrap
did not obey the umask
chmod u+rw /tmp/kpop4.*
Compose a keystore for import
openssl pkcs12 -export -in /tmp/kpop4.cert -inkey /tmp/kpop4.key -out /tmp/kpop4.p12 -name "kpop4"
In Chrome security settings > certs > my certs > import
Then visit ZAC with userscript @match
of the mgmt API base URL
If a client cert is available in the web browser visiting the SPA in the controller's web API, then it will already have negotiated mTLS by the time the SPA is loaded.
The SPA could send
POST /edge/management/v1/authenticate?method=cert
with an empty body. If the response is not an error then.data.token
iszt-session
for subsequent requests.There's no harm in attempting this and failing silently, since there's no way to know if a client cert was available during TLS negotiation.
If it succeeds, then skip the password form and go straight to the dashboard.