Skip to content

Commit

Permalink
authentication should also life here for simplicity, making jolin rea…
Browse files Browse the repository at this point in the history
…lly only a higher meta package
  • Loading branch information
schlichtanders committed Jan 13, 2025
1 parent d035b3b commit 795d13d
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 29 deletions.
7 changes: 4 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "JolinPluto"
uuid = "5b0b4ef8-f4e6-4363-b674-3f031f7b9530"
authors = ["Stephan Sahm <[email protected]> and contributors"]
version = "0.1.92"
version = "0.1.93"

[deps]
AbstractPlutoDingetjes = "6e696c72-6542-2067-7265-42206c756150"
Expand All @@ -18,13 +18,14 @@ JWTs = "d850fbd6-035d-5a70-a269-1ca2e636ac6c"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[weakdeps]
AWS = "fbe9abb3-538b-5e4e-ba9e-bc94f4f92ebc"
PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
RCall = "6f49c342-dc21-5d91-9882-a32aef131414"
CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"

[extensions]
AWSExt = "AWS"
PythonCallExt = "PythonCall"
RCallExt = ["RCall", "CondaPkg"]
RCallExt = "RCall"

[compat]
julia = "1.7"
Expand Down
46 changes: 46 additions & 0 deletions ext/AWSExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module AWSExt
import Jolin, AWS, JSON3
using Dates

function Jolin.authenticate_aws(args...)
mydateformat = Dates.dateformat"yyyymmdd\THHMMSS\Z"
# we define a function in a macro, so that we can use @get_jwt macro (which needs the location)
# as well as use `renew`` argument, which requires a function
function _authenticate_aws(role_arn; audience="", role_session::Union{AbstractString,Nothing}=nothing)
if isnothing(role_session)
role_session = AWS._role_session_name(
"jolincloud-role-",
basename(role_arn),
"-" * Dates.format(Dates.now(Dates.UTC), mydateformat),
)
end
# we need to be cautious that @get_jwt is called with the same __source__
web_identity = Jolin.jolin_token(audience)

response = AWS.AWSServices.sts(
"AssumeRoleWithWebIdentity",
Dict(
"RoleArn" => role_arn,
"RoleSessionName" => role_session, # Required by AssumeRoleWithWebIdentity
"WebIdentityToken" => web_identity,
);
aws_config=AWS.AWSConfig(; creds=nothing),
feature_set=AWS.FeatureSet(; use_response_type=true),
)
dict = JSON3.read(response)
role_creds = dict["AssumeRoleWithWebIdentityResult"]["Credentials"]
assumed_role_user = dict["AssumeRoleWithWebIdentityResult"]["AssumedRoleUser"]

return AWS.global_aws_config(creds=AWS.AWSCredentials(
role_creds["AccessKeyId"],
role_creds["SecretAccessKey"],
role_creds["SessionToken"],
assumed_role_user["Arn"];
expiry=Dates.DateTime(rstrip(role_creds["Expiration"], 'Z')),
renew=() -> _authenticate_aws(role_arn; audience, role_session).credentials,
))
end
_authenticate_aws(args...)
end

end # module
23 changes: 0 additions & 23 deletions ext/PlotsExt.jl

This file was deleted.

3 changes: 1 addition & 2 deletions ext/RCallExt.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module RCallExt
import CondaPkg # only needed for initialization
import RCall, JolinPluto

function JolinPluto.ChannelWithRepeatedFill(get_next_value::RCall.RObject, args...; sleep_seconds=0.0, skip_value=JolinPluto.NoPut, kwargs...)
Expand All @@ -13,7 +12,7 @@ function JolinPluto.ChannelWithRepeatedFill(get_next_value::RCall.RObject, args.
end
end

# c(MD, HTML, format_html, viewof) %<-% julia_eval("Jolin.MD, Jolin._HTML, Jolin.format_html, Jolin.viewof")
# c(MD, HTML, format_html, viewof) %<-% julia_eval("Jolin.MD, s -> HTML(s), Jolin.format_html, Jolin.viewof")

JolinPluto.lang_enabled(::Val{:r}) = true
function JolinPluto.lang_set_global(::Val{:r}, def::Symbol, value)
Expand Down
4 changes: 3 additions & 1 deletion src/JolinPluto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ export repeat_take!, repeat_take, repeat_at, repeat_run, ChannelPluto, repeat_qu
export output_below, clipboard_image_to_clipboard_html, embedLargeHTML, plotly_responsive
export Setter, @get, @cell_ids_create_wrapper, @cell_ids_push!
export cell_ids_create_wrapper, cell_ids_push!, cell_ids_push
export MD, format_html, _HTML
export MD, format_html
export viewof
export IPyWidget, IPyWidget_init
export authenticate_token, authenticate_aws

using Dates, HTTP, JSON3, Git, JWTs, UUIDs, Base64
using HypertextLiteral, Continuables
Expand All @@ -19,5 +20,6 @@ include("tasks.jl")
include("setter.jl")
include("frontend.jl")
include("languages.jl")
include("authentication.jl")

end # module
68 changes: 68 additions & 0 deletions src/authentication.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import HTTP
import JSON3
using JWTs: JWT
using Git: git

"""
authenticate_token()
authenticate_token("exampleaudience")
Creates a JSON Web Token which can be used for authentication at common cloud providers.
On cloud.jolin.io the token will be issued and signed by cloud.jolin.io,
on Github Actions (used for automated tests), a respective github token is returned.
"""
function authenticate_token(audience="")
# Jolin Cloud
if parse(Bool, get(ENV, "JOLIN_CLOUD", "false"))
serviceaccount_token = readchomp("/var/run/secrets/kubernetes.io/serviceaccount/token")
path = Main.PlutoRunner.notebook_path[]
project_dir = readchomp(`$(git()) -C $(dirname(path)) rev-parse --show-toplevel`)
@assert startswith(path, project_dir) "invalid workflow location"
workflowpath = path[length(project_dir)+2:end]

response = HTTP.get("http://jolin-workspace-server-jwts.jolin-workspace-server/request_jwt",
query=["serviceaccount_token" => serviceaccount_token,
"workflowpath" => workflowpath,
"audience" => audience])
return JSON3.read(response.body).token

# Github Actions
elseif (parse(Bool, get(ENV, "CI", "false"))
&& haskey(ENV, "ACTIONS_ID_TOKEN_REQUEST_TOKEN")
&& haskey(ENV, "ACTIONS_ID_TOKEN_REQUEST_URL"))
response = HTTP.get(ENV["ACTIONS_ID_TOKEN_REQUEST_URL"],
query=["audience" => audience],
headers=["Authorization" => "bearer " * ENV["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]])
# the token is in subfield value https://blog.alexellis.io/deploy-without-credentials-using-oidc-and-github-actions/
return JSON3.read(response.body).value

# using a specific environment variable to set the token from the outside for local development purposes
elseif haskey(ENV, "JOLIN_JWT_FALLBACK")
return ENV["JOLIN_JWT_FALLBACK"]

# Fallback with Dummy Value
else
payload = Dict(
"iss" => "http://www.example.com/",
"sub" => "/env/YOUR_ENV/github.com/YOUR_ORGANIZATION/YOUR_REPO/PATH/TO/WORKFLOW",
"aud" => audience,
"exp" => 1536080651,
"iat" => 1535994251,
)
# for details see https://github.com/tanmaykm/JWTs.jl/issues/22
jwt = JWT(; payload)
return ".$jwt."
end
end


"""
authenticate_aws(role_arn; audience="")
Assume role via web identity. How to define such a role can be found here
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-oidc
"""
function authenticate_aws end

# TODO add Azure, Google Cloud and HashiCorp

0 comments on commit 795d13d

Please sign in to comment.