2 changed files with 300 additions and 26 deletions
@ -1,33 +1,304 @@ |
|||||
error_log stderr ${LOG_LEVEL}; |
error_log stderr ${LOG_LEVEL}; |
||||
|
|
||||
worker_processes 1; |
worker_processes 1; |
||||
|
|
||||
events { |
events { |
||||
worker_connections 1024; |
worker_connections 1024; |
||||
} |
} |
||||
|
|
||||
http { |
http { |
||||
default_type text/plain; |
default_type text/plain; |
||||
sendfile on; |
sendfile on; |
||||
keepalive_timeout 65; |
keepalive_timeout 65; |
||||
resolver ${RESOLVER} ipv6=off; |
resolver ${RESOLVER} ipv6=off; |
||||
|
|
||||
server { |
server { |
||||
listen 8443 ssl; |
listen 8443 ssl; |
||||
server_name ${PROXY_ROUTE_HOSTNAME}; |
server_name ${PROXY_ROUTE_HOSTNAME}; |
||||
|
|
||||
ssl on; |
ssl on; |
||||
ssl_certificate ${APP_ROOT}/etc/serving-cert/tls.crt; |
ssl_certificate ${APP_ROOT}/etc/serving-cert/tls.crt; |
||||
ssl_certificate_key ${APP_ROOT}/etc/serving-cert/tls.key; |
ssl_certificate_key ${APP_ROOT}/etc/serving-cert/tls.key; |
||||
|
|
||||
location / { |
# Enable SSL/TLS Client Certificates Authentication |
||||
proxy_pass http://${SSO_SERVICE_HOSTNAME}; |
ssl_verify_client optional; |
||||
proxy_set_header Host $host; |
ssl_client_certificate ${APP_ROOT}/etc/ca-certs/ca-bundle.pem; |
||||
proxy_set_header X-Real-IP $remote_addr; |
ssl_crl ${APP_ROOT}/etc/ca-certs/crl.pem; |
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|
||||
proxy_set_header X-Forwarded-Proto $scheme; |
location ~ ^/auth/realms/(${SSO_REALMS})/register$ { |
||||
} |
access_by_lua_block { |
||||
|
ngx.log(ngx.INFO, "VERIFY: ", ngx.var.ssl_client_verify) |
||||
|
if ngx.var.ssl_client_verify ~= "SUCCESS" then |
||||
|
ngx.status = ngx.HTTP_FORBIDDEN |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"You need to authenticate using an SSL/TLS Client Certificate."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
ngx.log(ngx.INFO, "Authenticated Client : ", ngx.var.ssl_client_s_dn) |
||||
|
} |
||||
|
|
||||
|
content_by_lua_block { |
||||
|
local realm = ngx.var[1]; -- captured from the location regex |
||||
|
ngx.log(ngx.INFO, "Received a registration request for realm " .. realm) |
||||
|
if ngx.req.get_method() ~= "POST" then |
||||
|
ngx.status = ngx.HTTP_NOT_ALLOWED |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"Only POST requests are accepted."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
if ngx.req.get_headers()["Content-Type"] ~= "application/x-www-form-urlencoded" then |
||||
|
ngx.status = 415 -- Unsupported Media |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"Wrong content-type. Must be application/x-www-form-urlencoded."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
ngx.req.read_body() |
||||
|
local args, err = ngx.req.get_post_args() |
||||
|
if not args then |
||||
|
ngx.status = ngx.HTTP_BAD_REQUEST |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"No post parameters found."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
if not args['apikey'] or args['apikey'] == '' then |
||||
|
ngx.status = ngx.HTTP_BAD_REQUEST |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"No apikey parameter found in the request."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
if not args['client_id'] or args['client_id'] == '' then |
||||
|
ngx.status = ngx.HTTP_BAD_REQUEST |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"No client_id parameter found in the request."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
local apikey = args['apikey'] |
||||
|
local client_id = args['client_id'] |
||||
|
|
||||
|
local http = require "resty.http" |
||||
|
local httpc = http.new() |
||||
|
|
||||
|
local res, err = httpc:request_uri("${BACKEND_ENDPOINT_OVERRIDE}/transactions/authrep.xml", { |
||||
|
query = { |
||||
|
service_token = "${THREESCALE_SERVICE_TOKEN}", |
||||
|
service_id = "${THREESCALE_SERVICE_ID}", |
||||
|
user_key = apikey |
||||
|
}, |
||||
|
method = "GET", |
||||
|
ssl_verify = false |
||||
|
}) |
||||
|
|
||||
|
if not res then |
||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Could not get a response from the 3scale backend."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
if res.status ~= ngx.HTTP_OK then |
||||
|
ngx.status = ngx.HTTP_FORBIDDEN |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Denied by the 3scale backend."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
res, err = httpc:request_uri("${THREESCALE_PORTAL_ENDPOINT}/admin/api/applications/find.json", { |
||||
|
query = { |
||||
|
access_token = "${THREESCALE_ACCESS_TOKEN}", |
||||
|
user_key = apikey |
||||
|
}, |
||||
|
method = "GET", |
||||
|
ssl_verify = false |
||||
|
}) |
||||
|
|
||||
|
if not res then |
||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Could not get a response from the 3scale API."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
local cjson = require 'cjson' |
||||
|
local json_body = cjson.decode(res.body) |
||||
|
local apikey_account_id = json_body['application'] and json_body['application']['account_id'] |
||||
|
|
||||
|
res, err = httpc:request_uri("${THREESCALE_PORTAL_ENDPOINT}/admin/api/applications/find.json", { |
||||
|
query = { |
||||
|
access_token = "${THREESCALE_ACCESS_TOKEN}", |
||||
|
app_id = client_id |
||||
|
}, |
||||
|
method = "GET", |
||||
|
ssl_verify = false |
||||
|
}) |
||||
|
|
||||
|
if not res then |
||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Could not get a response from the 3scale API."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
json_body = cjson.decode(res.body) |
||||
|
local client_account_id = json_body['application'] and json_body['application']['account_id'] |
||||
|
|
||||
|
if client_account_id ~= apikey_account_id then |
||||
|
ngx.status = ngx.HTTP_FORBIDDEN |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"The apikey and client_id do not belong to the same account."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}/auth/realms/"..realm.."/protocol/openid-connect/token", { |
||||
|
body = ngx.encode_args({ |
||||
|
client_id = "${SSO_CLIENT_ID}", |
||||
|
username = "${SSO_SERVICE_USERNAME}", |
||||
|
password = "${SSO_SERVICE_PASSWORD}", |
||||
|
grant_type = "password" |
||||
|
}), |
||||
|
headers = { |
||||
|
["Content-Type"] = "application/x-www-form-urlencoded", |
||||
|
}, |
||||
|
method = "POST", |
||||
|
ssl_verify = false |
||||
|
}) |
||||
|
|
||||
|
if not res or res.status ~= ngx.HTTP_OK then |
||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Could not get an access token with admin privileges on RH-SSO."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
json_body = cjson.decode(res.body) |
||||
|
local access_token = json_body['access_token'] |
||||
|
|
||||
|
res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}/auth/admin/realms/"..realm.."/clients", { |
||||
|
query = { |
||||
|
clientId = client_id |
||||
|
}, |
||||
|
headers = { |
||||
|
["Authorization"] = "Bearer " .. access_token |
||||
|
}, |
||||
|
method = "GET", |
||||
|
ssl_verify = false |
||||
|
}) |
||||
|
|
||||
|
if not res or res.status ~= ngx.HTTP_OK then |
||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Could not retrieve the list of clients in RH-SSO."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
json_body = cjson.decode(res.body) |
||||
|
local client_id_rhssoid = json_body[1] and json_body[1]['id'] |
||||
|
|
||||
|
if not client_id_rhssoid then |
||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Could not find the client in RH-SSO."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}/auth/admin/realms/"..realm.."/clients/" .. client_id_rhssoid .. "/certificates/jwt.credential", { |
||||
|
method = "GET", |
||||
|
headers = { |
||||
|
["Authorization"] = "Bearer " .. access_token |
||||
|
}, |
||||
|
ssl_verify = false |
||||
|
}) |
||||
|
|
||||
|
if not res or res.status ~= ngx.HTTP_OK then |
||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Could not check client certificates in RH-SSO."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
json_body = cjson.decode(res.body) |
||||
|
local existing_certificate = json_body['certificate'] |
||||
|
|
||||
|
if existing_certificate then |
||||
|
ngx.status = ngx.HTTP_FORBIDDEN |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"Already registered."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
local certificate = ngx.var.ssl_client_raw_cert |
||||
|
local start, len = string.find(certificate, "[-]+BEGIN CERTIFICATE[-]+") |
||||
|
start = start + len |
||||
|
local finish = string.find(certificate, "[-]+END CERTIFICATE[-]+") |
||||
|
certificate = string.sub(certificate, start, finish-1) |
||||
|
|
||||
|
local raw_certificate = "" |
||||
|
for i in string.gmatch(certificate, "[a-zA-Z0-9+/=]+") do |
||||
|
raw_certificate = raw_certificate .. i |
||||
|
end |
||||
|
|
||||
|
local boundary = string.format("--------------------------%s", ngx.var.request_id) |
||||
|
local content_type = "multipart/form-data; boundary=" .. boundary |
||||
|
|
||||
|
body = string.format('--%s\r\nContent-Disposition: form-data; name="file"\r\nContent-Type: application/octet-stream\r\n\r\n%s\r\n--%s\r\nContent-Disposition: form-data; name="keystoreFormat"\r\n\r\n%s\r\n--%s--\r\n', boundary, raw_certificate, boundary, "Certificate PEM", boundary) |
||||
|
ngx.say(body) |
||||
|
res, err = httpc:request_uri("http://${SSO_SERVICE_HOSTNAME}/auth/admin/realms/"..realm.."/clients/" .. client_id_rhssoid .. "/certificates/jwt.credential/upload", { |
||||
|
method = "POST", |
||||
|
body = body, |
||||
|
headers = { |
||||
|
["Authorization"] = "Bearer " .. access_token, |
||||
|
["Content-Type"] = content_type |
||||
|
}, |
||||
|
ssl_verify = false |
||||
|
}) |
||||
|
|
||||
|
if not res or res.status ~= ngx.HTTP_OK then |
||||
|
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"server_error","error_description":"Could not register certificate in RH-SSO."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
|
||||
|
ngx.status = ngx.HTTP_OK |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{ "status": "registered" }') |
||||
|
|
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
} |
||||
} |
} |
||||
} |
|
||||
|
|
||||
|
location ~ ^/auth/realms/(${SSO_REALMS})/protocol/openid-connect/token$ { |
||||
|
access_by_lua_block { |
||||
|
ngx.log(ngx.INFO, "VERIFY: ", ngx.var.ssl_client_verify) |
||||
|
if ngx.var.ssl_client_verify ~= "SUCCESS" then |
||||
|
ngx.status = ngx.HTTP_FORBIDDEN |
||||
|
ngx.header['Content-Type'] = 'application/json' |
||||
|
ngx.say('{"error":"invalid_request","error_description":"You need to authenticate using an SSL/TLS Client Certificate."}') |
||||
|
ngx.exit(ngx.HTTP_OK) |
||||
|
end |
||||
|
ngx.log(ngx.ERR, "Authenticated Client : ", ngx.var.ssl_client_s_dn) |
||||
|
} |
||||
|
|
||||
|
# Beware: no slash at the end of the proxy_pass directive since we don't want to rewrite URLs |
||||
|
proxy_pass http://${SSO_SERVICE_HOSTNAME}; |
||||
|
proxy_set_header Host $host; |
||||
|
proxy_set_header X-Real-IP $remote_addr; |
||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||
|
proxy_set_header X-Forwarded-Proto $scheme; |
||||
|
} |
||||
|
|
||||
|
location ~ ^/.* { |
||||
|
# Beware: no slash at the end of the proxy_pass directive since we don't want to rewrite URLs |
||||
|
proxy_pass http://${SSO_SERVICE_HOSTNAME}; |
||||
|
proxy_set_header Host $host; |
||||
|
proxy_set_header X-Real-IP $remote_addr; |
||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||
|
proxy_set_header X-Forwarded-Proto $scheme; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|||||
Loading…
Reference in new issue