From 3e176378e49e1853b54f9e300f83146d05056857 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sun, 15 Dec 2019 12:59:52 -0800 Subject: [PATCH 01/12] wip --- temp/jwtRS256.key | 5 + temp/jwtRS256.key.pub | 1 + temp/test1.key | 5 + temp/test1.key.pub | 1 + temp/test1.pem | 0 temp/test2.key | 5 + temp/test2.key.pub | 1 + tests/ScratchPad/Program.cs | 254 +++++++++++++++++++++-------- tests/ScratchPad/ScratchPad.csproj | 4 +- 9 files changed, 207 insertions(+), 69 deletions(-) create mode 100644 temp/jwtRS256.key create mode 100644 temp/jwtRS256.key.pub create mode 100644 temp/test1.key create mode 100644 temp/test1.key.pub create mode 100644 temp/test1.pem create mode 100644 temp/test2.key create mode 100644 temp/test2.key.pub diff --git a/temp/jwtRS256.key b/temp/jwtRS256.key new file mode 100644 index 0000000..d6302d6 --- /dev/null +++ b/temp/jwtRS256.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIGIxetO+Ve9EQY5tIA7uJJkyb+yjjeJpOhWDaBGlvAw/oAoGCCqGSM49 +AwEHoUQDQgAEp6ZTrRNJ1VS550siDtCE2BOTMThRqndZAiNIWUshMNYQTs6jaJGv +6Bl0R29Phy5bpmqCgNelDPOhA2NU8oiETw== +-----END EC PRIVATE KEY----- diff --git a/temp/jwtRS256.key.pub b/temp/jwtRS256.key.pub new file mode 100644 index 0000000..8fd0ef8 --- /dev/null +++ b/temp/jwtRS256.key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKemU60TSdVUuedLIg7QhNgTkzE4Uap3WQIjSFlLITDWEE7Oo2iRr+gZdEdvT4cuW6ZqgoDXpQzzoQNjVPKIhE8= danie@daniel-desktop diff --git a/temp/test1.key b/temp/test1.key new file mode 100644 index 0000000..ddc15c3 --- /dev/null +++ b/temp/test1.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49 +AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK41 +3FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw== +-----END EC PRIVATE KEY----- diff --git a/temp/test1.key.pub b/temp/test1.key.pub new file mode 100644 index 0000000..e38b025 --- /dev/null +++ b/temp/test1.key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBpZkp9ShVxbEFtRo4r09XBEvQA/0pf26sHGKnbAxUS+G+enGBiuNdxRTzUUEvE8KK4AQxyCUotbaAydfVs4S18= daniel@daniel-desktop diff --git a/temp/test1.pem b/temp/test1.pem new file mode 100644 index 0000000..e69de29 diff --git a/temp/test2.key b/temp/test2.key new file mode 100644 index 0000000..262e644 --- /dev/null +++ b/temp/test2.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEILZmFcSjtm9SZ0EI+pgFYxvwLt5Cr9twko1+KwJNdjBJoAoGCCqGSM49 +AwEHoUQDQgAEhcEuk1hs8QsZT24s96dnlORoFLF+Alh1wxVkKdSs0mH8CM7SEAOh +ONKi8xM1/kEDufovcKwvvxx+z3r1SvNpGA== +-----END EC PRIVATE KEY----- diff --git a/temp/test2.key.pub b/temp/test2.key.pub new file mode 100644 index 0000000..e934be6 --- /dev/null +++ b/temp/test2.key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIXBLpNYbPELGU9uLPenZ5TkaBSxfgJYdcMVZCnUrNJh/AjO0hADoTjSovMTNf5BA7n6L3CsL78cfs969UrzaRg= daniel@daniel-desktop diff --git a/tests/ScratchPad/Program.cs b/tests/ScratchPad/Program.cs index c32b160..429c60c 100644 --- a/tests/ScratchPad/Program.cs +++ b/tests/ScratchPad/Program.cs @@ -5,113 +5,231 @@ using System.Threading.Tasks; using Docker.DotNet; using Docker.DotNet.Models; +using Microsoft.IdentityModel.Tokens; +using System.Security.Claims; +using System.Text; +using Org.BouncyCastle.Utilities.IO.Pem; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using System.Security.Cryptography; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Sec; namespace ScratchPad { class Program { + static string PrivateKey = "MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="; + //static string PublicKey = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBpZkp9ShVxbEFtRo4r09XBEvQA/0pf26sHGKnbAxUS+G+enGBiuNdxRTzUUEvE8KK4AQxyCUotbaAydfVs4S18="; + static string RealPublicKey = "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="; + + static string Key2 = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G"; + static string Key2Pub = "hcEuk1hs8QsZT24s96dnlORoFLF+Alh1wxVkKdSs0mH8CM7SEAOhONKi8xM1/kEDufovcKwvvxx+z3r1SvNpGA=="; + static void Main(string[] args) { - Test().Wait(); - //Console.WriteLine(Environment.CurrentDirectory); - Console.ReadLine(); + MakeToken(); + //var token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJmaXJzdE5hbWUiOiJ0ZXN0IiwibGFzdE5hbWUiOiJ0ZXN0IiwibmJmIjoxNTc2NDQyODg5LCJleHAiOjE1NzY3MDIwODksImlhdCI6MTU3NjQ0Mjg4OX0.VK05y04Uxjsev8_kxOyk5UtkWJTwBWnJ47_riM5EwYEibJiusfbI_34PD2AM4iYiP_5RxkUIc6TiG2LG2FdbWg"; + //Console.WriteLine(VerifyToken(token)); } - static async Task Test() + static void MakeToken() { - var docker = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")).CreateClient(); - var net = await docker.Networks.CreateNetworkAsync(new NetworksCreateParameters() { Name = "testnet3" } ); - - var mongoContainerId = await StartMongo(docker, "conductor-tests-mongo"); - await docker.Networks.ConnectNetworkAsync(net.ID, new NetworkConnectParameters() {Container = mongoContainerId}); + var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); + var now = DateTime.UtcNow; - var ms = new FileStream(@"D:\dev\Conductor\Conductor.tar", FileMode.Open); + //var keyDataStr = File.ReadAllText(@"C:\dev\jwt\test1.key"); + //var tr = new System.IO.StringReader(keyDataStr); + //var pr = new Org.BouncyCastle.OpenSsl.PemReader(tr); + //var pem = pr.ReadPemObject(); + var e1 = ECDsa.Create(); // (ECCurve.NamedCurves.nistP256); + //var privKey = Convert.FromBase64String(PrivateKey); + var privKey = Convert.FromBase64String(Key2); + e1.ImportECPrivateKey(privKey, out int rb1); + + var params1 = e1.ExportParameters(false); + var pubStr = Convert.ToBase64String(params1.Q.X.Concat(params1.Q.Y).ToArray()); + Console.WriteLine(pubStr); + Console.WriteLine(); + + + var key = new ECDsaSecurityKey(e1); + + var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); - var build = await docker.Images.BuildImageFromDockerfileAsync(ms, new ImageBuildParameters() + var tokenDescriptor = new SecurityTokenDescriptor { - Tags = new List() { "poc" } - }); + Subject = new ClaimsIdentity(new[] + { + new Claim( "email", "test@test.com" ), + new Claim( "firstName", "test" ), + new Claim( "lastName", "test" ) + }), + Expires = now.AddDays(3), + SigningCredentials = sc, + }; + + var token = tokenHandler.CreateToken(tokenDescriptor); + + var tokenString = tokenHandler.WriteToken(token); + + Console.WriteLine(tokenString); + + Console.ReadLine(); + } + + public static bool VerifyToken(string jwt) + { + string[] jwtParts = jwt.Split('.'); - using (StreamReader sr = new StreamReader(build)) + //var sha256 = SHA256.Create(); + //var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(jwtParts[0] + '.' + jwtParts[1])); + //var a = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken() + //Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateEncodedSignature() + + //var keyDataStr = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBpZkp9ShVxbEFtRo4r09XBEvQA/0pf26sHGKnbAxUS+G+enGBiuNdxRTzUUEvE8KK4AQxyCUotbaAydfVs4S18="; //File.ReadAllText(@"C:\dev\jwt\test1.key.pub"); + var pubKey = Convert.FromBase64String(RealPublicKey); //(RealPublicKey); + var e1 = ECDsa.Create(); + e1.ImportParameters(new ECParameters() { - string line; - // Read and display lines from the file until the end of - // the file is reached. - while ((line = sr.ReadLine()) != null) + Curve = ECCurve.NamedCurves.nistP256, + Q = new ECPoint() { - Console.WriteLine(line); + X = pubKey.Take(32).ToArray(), + Y = pubKey.Skip(32).Take(32).ToArray() } - } + }); + + var key = new ECDsaSecurityKey(e1); + var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); - var hostCfg = new HostConfig(); - var pb = new PortBinding + var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); + var token = tokenHandler.ReadJwtToken(jwt); + + var tvp = new TokenValidationParameters() { - HostIP = "0.0.0.0", - HostPort = "8001" + IssuerSigningKey = key, + ValidateAudience = false, + ValidateIssuer = false }; + var cp = tokenHandler.ValidateToken(jwt, tvp, out var vt); + + + + return false; + + } - hostCfg.PortBindings = new Dictionary>(); - hostCfg.PortBindings.Add($"{80}/tcp", new PortBinding[] { pb }); - var container = await docker.Containers.CreateContainerAsync(new CreateContainerParameters() + private static ECDsa LoadPrivateKey(byte[] key) + { + var privKeyInt = new Org.BouncyCastle.Math.BigInteger(+1, key); + var parameters = SecNamedCurves.GetByName("secp256r1"); + var ecPoint = parameters.G.Multiply(privKeyInt); + var privKeyX = ecPoint.Normalize().XCoord.ToBigInteger().ToByteArrayUnsigned(); + var privKeyY = ecPoint.Normalize().YCoord.ToBigInteger().ToByteArrayUnsigned(); + + return ECDsa.Create(new ECParameters { - Name = "conductor-tests", - Image = "poc", - Env = new List() { $"DBHOST=mongodb://conductor-tests-mongo:27017/" }, - HostConfig = hostCfg, - NetworkingConfig = new NetworkingConfig() + Curve = ECCurve.NamedCurves.nistP256, + D = privKeyInt.ToByteArrayUnsigned(), + Q = new ECPoint { - EndpointsConfig = new Dictionary() - { - {"", new EndpointSettings() - { - Links = new List() { mongoContainerId }, - NetworkID = net.ID - } }, - } + X = privKeyX, + Y = privKeyY } }); + } - await docker.Networks.ConnectNetworkAsync(net.ID, new NetworkConnectParameters() { Container = container.ID }); - var started = await docker.Containers.StartContainerAsync(container.ID, new ContainerStartParameters()); - - + private static byte[] FromHexString(string hex) + { + var numberChars = hex.Length; + var hexAsBytes = new byte[numberChars / 2]; + for (var i = 0; i < numberChars; i += 2) + hexAsBytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); - Console.WriteLine("YAY!!!!"); - await Task.Delay(0); - - //net. + return hexAsBytes; } - static async Task StartMongo(DockerClient docker, string name) + private static ECDsa LoadPublicKey(byte[] key) { - //await docker.Images.CreateImageAsync(new ImagesCreateParameters() { FromImage = "mongo" }, null, new Progress()); + var pubKeyX = key.Skip(1).Take(32).ToArray(); + var pubKeyY = key.Skip(33).ToArray(); + return ECDsa.Create(new ECParameters + { + Curve = ECCurve.NamedCurves.nistP256, + Q = new ECPoint + { + X = pubKeyX, + Y = pubKeyY + } + }); + } - var existing = await docker.Containers.ListContainersAsync(new ContainersListParameters() { All = true }); - var old = existing.FirstOrDefault(x => x.Names.Any(y => y == $"/{name}")); - //if (old != null) - //{ - // old.ID - //} - //existing[0]. + private readonly DerObjectIdentifier _curveId = SecObjectIdentifiers.SecP256k1; - + public byte[] GeneratePrivateKey() + { + return SecureRandom.GetNextBytes(SecureRandom.GetInstance("SHA256PRNG"), 32); + } - var container = await docker.Containers.CreateContainerAsync(new CreateContainerParameters() - { - Image = $"mongo", - Name = name - }); - - var started = await docker.Containers.StartContainerAsync(container.ID, new ContainerStartParameters()); - if (started) + public byte[] BuildPrivateKeyFromPhrase(string phrase) + { + using (var hasher = System.Security.Cryptography.SHA256.Create()) { - return container.ID; + var privateKey = hasher.ComputeHash(Encoding.Unicode.GetBytes(phrase)); + return privateKey; } + } - throw new InvalidOperationException(); + public byte[] GetPublicKey(byte[] privateKey) + { + var privKeyInt = new BigInteger(privateKey); + var parameters = NistNamedCurves.GetByOid(_curveId); + var qa = parameters.G.Multiply(privKeyInt); + + return qa.GetEncoded(); + } + + public byte[] Sign(byte[] data, byte[] privateKey) + { + var cp = GetPrivateKeyParameters(privateKey); + var signer = SignerUtilities.GetSigner("ECDSA"); + signer.Init(true, cp); + signer.BlockUpdate(data, 0, data.Length); + return signer.GenerateSignature(); + } + + public bool Verify(byte[] data, byte[] sig, byte[] publicKey) + { + var cp = GetPublicKeyParams(publicKey); + var signer = SignerUtilities.GetSigner("ECDSA"); + signer.Init(false, cp); + signer.BlockUpdate(data, 0, data.Length); + return signer.VerifySignature(sig); } + + private ECPrivateKeyParameters GetPrivateKeyParameters(byte[] privateKey) + { + var parameters = NistNamedCurves.GetByOid(_curveId); + var privKeyInt = new BigInteger(privateKey); + var dp = new ECDomainParameters(parameters.Curve, parameters.G, parameters.N); + return new ECPrivateKeyParameters("ECDSA", privKeyInt, dp); + } + + private ECPublicKeyParameters GetPublicKeyParams(byte[] publicKey) + { + var parameters = NistNamedCurves.GetByOid(_curveId); + var dp = new ECDomainParameters(parameters.Curve, parameters.G, parameters.N); + var q = parameters.Curve.DecodePoint(publicKey); + return new ECPublicKeyParameters("ECDSA", q, dp); + } + + } } diff --git a/tests/ScratchPad/ScratchPad.csproj b/tests/ScratchPad/ScratchPad.csproj index e77a89f..e8b5582 100644 --- a/tests/ScratchPad/ScratchPad.csproj +++ b/tests/ScratchPad/ScratchPad.csproj @@ -2,11 +2,13 @@ Exe - netcoreapp2.2 + netcoreapp3.1 + + From 507a380ae3f91c88c00588b0a342b6f37f38612b Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sun, 22 Dec 2019 13:46:44 -0800 Subject: [PATCH 02/12] wip --- src/Conductor/Conductor.csproj | 2 + src/Conductor/Controllers/InfoController.cs | 2 + src/Conductor/Startup.cs | 70 ++++++++++++++++++++- src/Conductor/appsettings.json | 4 +- tests/ScratchPad/Program.cs | 6 +- 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/Conductor/Conductor.csproj b/src/Conductor/Conductor.csproj index 477d9c9..538881e 100644 --- a/src/Conductor/Conductor.csproj +++ b/src/Conductor/Conductor.csproj @@ -15,7 +15,9 @@ + + diff --git a/src/Conductor/Controllers/InfoController.cs b/src/Conductor/Controllers/InfoController.cs index 70fd05f..d0051d6 100644 --- a/src/Conductor/Controllers/InfoController.cs +++ b/src/Conductor/Controllers/InfoController.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading.Tasks; using Conductor.Models; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Conductor.Controllers @@ -14,6 +15,7 @@ namespace Conductor.Controllers public class InfoController : ControllerBase { [HttpGet] + [Authorize] public ActionResult Get() { var process = Process.GetCurrentProcess(); diff --git a/src/Conductor/Startup.cs b/src/Conductor/Startup.cs index 442637b..989e59e 100644 --- a/src/Conductor/Startup.cs +++ b/src/Conductor/Startup.cs @@ -21,6 +21,10 @@ using Microsoft.Extensions.Options; using WorkflowCore.Interface; using Newtonsoft.Json; +using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using System.Security.Cryptography; +using Microsoft.IdentityModel.Tokens; namespace Conductor { @@ -53,6 +57,60 @@ public void ConfigureServices(IServiceCollection services) .AddNewtonsoftJson() .SetCompatibilityVersion(CompatibilityVersion.Latest); + if (Configuration.GetValue("AuthEnabled")) + { + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + var publicKey = Convert.FromBase64String(Configuration.GetValue("IssuerKey")); + var e1 = ECDsa.Create(); + e1.ImportParameters(new ECParameters() + { + Curve = ECCurve.NamedCurves.nistP256, + Q = new ECPoint() + { + X = publicKey.Take(32).ToArray(), + Y = publicKey.Skip(32).Take(32).ToArray() + } + }); + + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new ECDsaSecurityKey(e1), + ValidateIssuer = false, + ValidateAudience = false + }; + options.Events = new JwtBearerEvents() + { + OnChallenge = context => + { + + return Task.CompletedTask; + }, + OnMessageReceived = context => + { + context.Token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJmaXJzdE5hbWUiOiJ0ZXN0IiwibGFzdE5hbWUiOiJ0ZXN0IiwibmJmIjoxNTc3MDQ4NDk3LCJleHAiOjE2NzE3NDI4OTcsImlhdCI6MTU3NzA0ODQ5N30.S2UtNp4MybQgOKz43_oC5aLeeN6DKL24UIKZ1_UPcHd9DB0j7gP6SEkutpmAXVb6YQvWcVl2LIo6BdkWXD4F_g"; + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + return Task.CompletedTask; + }, + + }; + options.RequireHttpsMetadata = false; + options.SaveToken = true; + + options.Validate(); + }); + } + services.AddWorkflow(cfg => { cfg.UseMongoDB(dbConnectionStr, Configuration.GetValue("DbName")); @@ -82,7 +140,7 @@ public void ConfigureServices(IServiceCollection services) } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime) { if (env.IsDevelopment()) { @@ -96,7 +154,15 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplica //app.UseHttpsRedirection(); app.UseMvc(); - + app.UseAuthentication(); + app.UseAuthorization(); + + + app.UseCors(x => x + .AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + var host = app.ApplicationServices.GetService(); var defService = app.ApplicationServices.GetService(); var backplane = app.ApplicationServices.GetService(); diff --git a/src/Conductor/appsettings.json b/src/Conductor/appsettings.json index 7626075..fa7fd94 100644 --- a/src/Conductor/appsettings.json +++ b/src/Conductor/appsettings.json @@ -6,5 +6,7 @@ }, "AllowedHosts": "*", "DbConnectionString": "mongodb://localhost:27017/", - "DbName": "conductor" + "DbName": "conductor", + "AuthEnabled": true, + "IssuerKey": "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw==" } diff --git a/tests/ScratchPad/Program.cs b/tests/ScratchPad/Program.cs index 429c60c..1d28554 100644 --- a/tests/ScratchPad/Program.cs +++ b/tests/ScratchPad/Program.cs @@ -46,8 +46,8 @@ static void MakeToken() //var pr = new Org.BouncyCastle.OpenSsl.PemReader(tr); //var pem = pr.ReadPemObject(); var e1 = ECDsa.Create(); // (ECCurve.NamedCurves.nistP256); - //var privKey = Convert.FromBase64String(PrivateKey); - var privKey = Convert.FromBase64String(Key2); + var privKey = Convert.FromBase64String(PrivateKey); + //var privKey = Convert.FromBase64String(Key2); e1.ImportECPrivateKey(privKey, out int rb1); var params1 = e1.ExportParameters(false); @@ -68,7 +68,7 @@ static void MakeToken() new Claim( "firstName", "test" ), new Claim( "lastName", "test" ) }), - Expires = now.AddDays(3), + Expires = now.AddYears(3), SigningCredentials = sc, }; From aa825bc9362de3a2436834afde3e66061669bb3b Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sun, 22 Dec 2019 17:07:16 -0800 Subject: [PATCH 03/12] wip --- src/Conductor/AuthExtensions.cs | 105 ++++++++++++++++++ src/Conductor/Controllers/InfoController.cs | 3 +- .../Controllers/WorkflowController.cs | 2 + src/Conductor/Startup.cs | 66 ++--------- src/Conductor/appsettings.json | 2 +- tests/ScratchPad/Program.cs | 2 +- 6 files changed, 121 insertions(+), 59 deletions(-) create mode 100644 src/Conductor/AuthExtensions.cs diff --git a/src/Conductor/AuthExtensions.cs b/src/Conductor/AuthExtensions.cs new file mode 100644 index 0000000..be91d5c --- /dev/null +++ b/src/Conductor/AuthExtensions.cs @@ -0,0 +1,105 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text.Encodings.Web; +using System.Threading.Tasks; + +namespace Conductor +{ + public static class AuthExtensions + { + public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builder, IConfiguration config) + { + builder.AddJwtBearer(options => + { + var publicKey = Convert.FromBase64String(config.GetValue("IssuerKey")); + var e1 = ECDsa.Create(); + e1.ImportParameters(new ECParameters() + { + Curve = ECCurve.NamedCurves.nistP256, + Q = new ECPoint() + { + X = publicKey.Take(32).ToArray(), + Y = publicKey.Skip(32).Take(32).ToArray() + } + }); + + options.IncludeErrorDetails = true; + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new ECDsaSecurityKey(e1), + ValidateIssuer = false, + ValidateAudience = false + }; + + options.RequireHttpsMetadata = false; + options.SaveToken = true; + + options.Validate(); + }); + + return builder; + } + + public static AuthenticationBuilder AddBypassAuth(this AuthenticationBuilder builder) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var privateKey = Convert.FromBase64String("MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="); + + var e1 = ECDsa.Create(); + e1.ImportECPrivateKey(privateKey, out int rb1); + + var key = new ECDsaSecurityKey(e1); + var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] + { + new Claim( ClaimTypes.Role, "admin" ), + new Claim( ClaimTypes.Role, "user" ), + }), + SigningCredentials = sc, + }; + + var token = tokenHandler.CreateToken(tokenDescriptor); + var tokenString = tokenHandler.WriteToken(token); + + builder.AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = false, + IssuerSigningKey = new ECDsaSecurityKey(e1), + ValidateIssuer = false, + ValidateAudience = false + }; + options.RequireHttpsMetadata = false; + options.Events = new JwtBearerEvents() + { + OnMessageReceived = context => + { + context.Token = tokenString; + return Task.CompletedTask; + } + }; + options.Validate(); + }); + + return builder; + } + } + +} diff --git a/src/Conductor/Controllers/InfoController.cs b/src/Conductor/Controllers/InfoController.cs index d0051d6..c10fabe 100644 --- a/src/Conductor/Controllers/InfoController.cs +++ b/src/Conductor/Controllers/InfoController.cs @@ -14,8 +14,7 @@ namespace Conductor.Controllers [ApiController] public class InfoController : ControllerBase { - [HttpGet] - [Authorize] + [HttpGet] public ActionResult Get() { var process = Process.GetCurrentProcess(); diff --git a/src/Conductor/Controllers/WorkflowController.cs b/src/Conductor/Controllers/WorkflowController.cs index ab27892..50aa9fa 100644 --- a/src/Conductor/Controllers/WorkflowController.cs +++ b/src/Conductor/Controllers/WorkflowController.cs @@ -7,6 +7,7 @@ using Conductor.Domain.Interfaces; using Conductor.Domain.Models; using Conductor.Models; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; @@ -16,6 +17,7 @@ namespace Conductor.Controllers { [Route("api/[controller]")] [ApiController] + [Authorize] public class WorkflowController : ControllerBase { private readonly IWorkflowController _workflowController; diff --git a/src/Conductor/Startup.cs b/src/Conductor/Startup.cs index 989e59e..c1cd6c6 100644 --- a/src/Conductor/Startup.cs +++ b/src/Conductor/Startup.cs @@ -56,60 +56,18 @@ public void ConfigureServices(IServiceCollection services) }) .AddNewtonsoftJson() .SetCompatibilityVersion(CompatibilityVersion.Latest); + + var authConfig = services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }); if (Configuration.GetValue("AuthEnabled")) - { - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .AddJwtBearer(options => - { - var publicKey = Convert.FromBase64String(Configuration.GetValue("IssuerKey")); - var e1 = ECDsa.Create(); - e1.ImportParameters(new ECParameters() - { - Curve = ECCurve.NamedCurves.nistP256, - Q = new ECPoint() - { - X = publicKey.Take(32).ToArray(), - Y = publicKey.Skip(32).Take(32).ToArray() - } - }); - - - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new ECDsaSecurityKey(e1), - ValidateIssuer = false, - ValidateAudience = false - }; - options.Events = new JwtBearerEvents() - { - OnChallenge = context => - { - - return Task.CompletedTask; - }, - OnMessageReceived = context => - { - context.Token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJmaXJzdE5hbWUiOiJ0ZXN0IiwibGFzdE5hbWUiOiJ0ZXN0IiwibmJmIjoxNTc3MDQ4NDk3LCJleHAiOjE2NzE3NDI4OTcsImlhdCI6MTU3NzA0ODQ5N30.S2UtNp4MybQgOKz43_oC5aLeeN6DKL24UIKZ1_UPcHd9DB0j7gP6SEkutpmAXVb6YQvWcVl2LIo6BdkWXD4F_g"; - return Task.CompletedTask; - }, - OnAuthenticationFailed = context => - { - return Task.CompletedTask; - }, - - }; - options.RequireHttpsMetadata = false; - options.SaveToken = true; + authConfig.AddJwtAuth(Configuration); + else + authConfig.AddBypassAuth(); - options.Validate(); - }); - } services.AddWorkflow(cfg => { @@ -151,12 +109,10 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApp // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. //app.UseHsts(); } - + + app.UseAuthentication(); //app.UseHttpsRedirection(); app.UseMvc(); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseCors(x => x .AllowAnyOrigin() diff --git a/src/Conductor/appsettings.json b/src/Conductor/appsettings.json index fa7fd94..f4c503c 100644 --- a/src/Conductor/appsettings.json +++ b/src/Conductor/appsettings.json @@ -7,6 +7,6 @@ "AllowedHosts": "*", "DbConnectionString": "mongodb://localhost:27017/", "DbName": "conductor", - "AuthEnabled": true, + "AuthEnabled": false, "IssuerKey": "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw==" } diff --git a/tests/ScratchPad/Program.cs b/tests/ScratchPad/Program.cs index 1d28554..2c1d445 100644 --- a/tests/ScratchPad/Program.cs +++ b/tests/ScratchPad/Program.cs @@ -55,7 +55,7 @@ static void MakeToken() Console.WriteLine(pubStr); Console.WriteLine(); - + //ClaimTypes.Role var key = new ECDsaSecurityKey(e1); var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); From 44339c4191397fbb8d003c4cde1a7b6294e23a8d Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sun, 22 Dec 2019 17:21:54 -0800 Subject: [PATCH 04/12] wip --- src/Conductor/AuthExtensions.cs | 15 ++++++--------- src/Conductor/Controllers/InfoController.cs | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Conductor/AuthExtensions.cs b/src/Conductor/AuthExtensions.cs index be91d5c..d4ce830 100644 --- a/src/Conductor/AuthExtensions.cs +++ b/src/Conductor/AuthExtensions.cs @@ -56,13 +56,9 @@ public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builde public static AuthenticationBuilder AddBypassAuth(this AuthenticationBuilder builder) { var tokenHandler = new JwtSecurityTokenHandler(); - var privateKey = Convert.FromBase64String("MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="); - - var e1 = ECDsa.Create(); - e1.ImportECPrivateKey(privateKey, out int rb1); - - var key = new ECDsaSecurityKey(e1); - var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); + var secret = Convert.FromBase64String("MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="); + var securityKey = new SymmetricSecurityKey(secret); + var sc = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var tokenDescriptor = new SecurityTokenDescriptor { @@ -82,9 +78,10 @@ public static AuthenticationBuilder AddBypassAuth(this AuthenticationBuilder bui options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = false, - IssuerSigningKey = new ECDsaSecurityKey(e1), + IssuerSigningKey = securityKey, ValidateIssuer = false, - ValidateAudience = false + ValidateAudience = false, + ValidateLifetime = false, }; options.RequireHttpsMetadata = false; options.Events = new JwtBearerEvents() diff --git a/src/Conductor/Controllers/InfoController.cs b/src/Conductor/Controllers/InfoController.cs index c10fabe..dc7b29c 100644 --- a/src/Conductor/Controllers/InfoController.cs +++ b/src/Conductor/Controllers/InfoController.cs @@ -14,7 +14,7 @@ namespace Conductor.Controllers [ApiController] public class InfoController : ControllerBase { - [HttpGet] + [HttpGet] public ActionResult Get() { var process = Process.GetCurrentProcess(); From c8b31f2862eba5623cfb3885211cf2f7fed4ccec Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Mon, 23 Dec 2019 08:01:29 -0800 Subject: [PATCH 05/12] wip --- src/Conductor/{ => Auth}/AuthExtensions.cs | 14 ++++++++------ src/Conductor/Auth/Roles.cs | 17 +++++++++++++++++ .../Controllers/DefinitionController.cs | 6 ++++++ src/Conductor/Controllers/EventController.cs | 3 +++ src/Conductor/Controllers/InfoController.cs | 2 +- src/Conductor/Controllers/StepController.cs | 5 +++++ src/Conductor/Controllers/WorkflowController.cs | 6 ++++++ src/Conductor/Startup.cs | 1 + src/Conductor/appsettings.json | 2 +- 9 files changed, 48 insertions(+), 8 deletions(-) rename src/Conductor/{ => Auth}/AuthExtensions.cs (87%) create mode 100644 src/Conductor/Auth/Roles.cs diff --git a/src/Conductor/AuthExtensions.cs b/src/Conductor/Auth/AuthExtensions.cs similarity index 87% rename from src/Conductor/AuthExtensions.cs rename to src/Conductor/Auth/AuthExtensions.cs index d4ce830..da268e8 100644 --- a/src/Conductor/AuthExtensions.cs +++ b/src/Conductor/Auth/AuthExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using Org.BouncyCastle.Security; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; @@ -14,7 +15,7 @@ using System.Text.Encodings.Web; using System.Threading.Tasks; -namespace Conductor +namespace Conductor.Auth { public static class AuthExtensions { @@ -22,7 +23,7 @@ public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builde { builder.AddJwtBearer(options => { - var publicKey = Convert.FromBase64String(config.GetValue("IssuerKey")); + var publicKey = Convert.FromBase64String(config.GetValue("AuthPublicKey")); var e1 = ECDsa.Create(); e1.ImportParameters(new ECParameters() { @@ -56,16 +57,17 @@ public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builde public static AuthenticationBuilder AddBypassAuth(this AuthenticationBuilder builder) { var tokenHandler = new JwtSecurityTokenHandler(); - var secret = Convert.FromBase64String("MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="); - var securityKey = new SymmetricSecurityKey(secret); + var securityKey = new SymmetricSecurityKey(new byte[121]); var sc = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { - new Claim( ClaimTypes.Role, "admin" ), - new Claim( ClaimTypes.Role, "user" ), + new Claim( ClaimTypes.Role, Roles.Admin), + new Claim( ClaimTypes.Role, Roles.Author), + new Claim( ClaimTypes.Role, Roles.Controller), + new Claim( ClaimTypes.Role, Roles.Viewer), }), SigningCredentials = sc, }; diff --git a/src/Conductor/Auth/Roles.cs b/src/Conductor/Auth/Roles.cs new file mode 100644 index 0000000..e7552ce --- /dev/null +++ b/src/Conductor/Auth/Roles.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Conductor.Auth +{ + public static class Roles + { + public const string Admin = "Admin"; + public const string Viewer = "Viewer"; + public const string Controller = "Controller"; + public const string Author = "Author"; + + public const string ControllerOrViewer = "Controller,Viewer"; + } +} diff --git a/src/Conductor/Controllers/DefinitionController.cs b/src/Conductor/Controllers/DefinitionController.cs index fbba97f..b6eb7e6 100644 --- a/src/Conductor/Controllers/DefinitionController.cs +++ b/src/Conductor/Controllers/DefinitionController.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Conductor.Auth; using Conductor.Domain.Interfaces; using Conductor.Domain.Models; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -21,12 +23,14 @@ public DefinitionController(IDefinitionService service) } [HttpGet] + [Authorize(Roles = Roles.Author)] public ActionResult> Get() { return new string[] { "value1", "value2" }; } [HttpGet("{id}")] + [Authorize(Roles = Roles.Author)] public ActionResult Get(string id) { var result = _service.GetDefinition(id); @@ -38,6 +42,7 @@ public ActionResult Get(string id) } [HttpPost] + [Authorize(Roles = Roles.Author)] public void Post([FromBody] Definition value) { _service.RegisterNewDefinition(value); @@ -52,6 +57,7 @@ public void Post([FromBody] Definition value) // DELETE api/values/5 [HttpDelete("{id}")] + [Authorize(Roles = Roles.Author)] public void Delete(int id) { } diff --git a/src/Conductor/Controllers/EventController.cs b/src/Conductor/Controllers/EventController.cs index e5cf5b5..a0a8f5c 100644 --- a/src/Conductor/Controllers/EventController.cs +++ b/src/Conductor/Controllers/EventController.cs @@ -3,8 +3,10 @@ using System.Dynamic; using System.Linq; using System.Threading.Tasks; +using Conductor.Auth; using Conductor.Domain.Interfaces; using Conductor.Domain.Models; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; @@ -23,6 +25,7 @@ public EventController(IWorkflowController workflowController) _workflowController = workflowController; } + [Authorize(Roles = Roles.Controller)] [HttpPost("{name}/{key}")] public async Task Post(string name, string key, [FromBody] object data) { diff --git a/src/Conductor/Controllers/InfoController.cs b/src/Conductor/Controllers/InfoController.cs index dc7b29c..c10fabe 100644 --- a/src/Conductor/Controllers/InfoController.cs +++ b/src/Conductor/Controllers/InfoController.cs @@ -14,7 +14,7 @@ namespace Conductor.Controllers [ApiController] public class InfoController : ControllerBase { - [HttpGet] + [HttpGet] public ActionResult Get() { var process = Process.GetCurrentProcess(); diff --git a/src/Conductor/Controllers/StepController.cs b/src/Conductor/Controllers/StepController.cs index b08a5cf..8509fc3 100644 --- a/src/Conductor/Controllers/StepController.cs +++ b/src/Conductor/Controllers/StepController.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Conductor.Auth; using Conductor.Domain.Interfaces; using Conductor.Domain.Models; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -13,6 +15,7 @@ namespace Conductor.Controllers { [Route("api/[controller]")] [ApiController] + [Authorize] public class StepController : ControllerBase { private readonly ICustomStepService _service; @@ -24,6 +27,7 @@ public StepController(ICustomStepService service) [HttpGet("{name}")] + [Authorize(Roles = Roles.Author)] public IActionResult Get(string name) { var resource = _service.GetStepResource(name); @@ -37,6 +41,7 @@ public IActionResult Get(string name) } [HttpPost("{name}")] + [Authorize(Roles = Roles.Author)] public async void Post(string name) { using (var sr = new StreamReader(Request.Body)) diff --git a/src/Conductor/Controllers/WorkflowController.cs b/src/Conductor/Controllers/WorkflowController.cs index 50aa9fa..98a9427 100644 --- a/src/Conductor/Controllers/WorkflowController.cs +++ b/src/Conductor/Controllers/WorkflowController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using AutoMapper; +using Conductor.Auth; using Conductor.Domain.Interfaces; using Conductor.Domain.Models; using Conductor.Models; @@ -32,6 +33,7 @@ public WorkflowController(IWorkflowController workflowController, IPersistencePr } [HttpGet("{id}")] + [Authorize(Roles = Roles.ControllerOrViewer)] public async Task> Get(string id) { var result = await _persistenceProvider.GetWorkflowInstance(id); @@ -42,6 +44,7 @@ public async Task> Get(string id) } [HttpPost("{id}")] + [Authorize(Roles = Roles.Controller)] public async Task> Post(string id, [FromBody] ExpandoObject data) { var instanceId = await _workflowController.StartWorkflow(id, data); @@ -51,6 +54,7 @@ public async Task> Post(string id, [FromBody] Exp } [HttpPut("{id}/suspend")] + [Authorize(Roles = Roles.Controller)] public async Task Suspend(string id) { var result = await _workflowController.SuspendWorkflow(id); @@ -61,6 +65,7 @@ public async Task Suspend(string id) } [HttpPut("{id}/resume")] + [Authorize(Roles = Roles.Controller)] public async Task Resume(string id) { var result = await _workflowController.ResumeWorkflow(id); @@ -71,6 +76,7 @@ public async Task Resume(string id) } [HttpDelete("{id}")] + [Authorize(Roles = Roles.Controller)] public async Task Terminate(string id) { var result = await _workflowController.TerminateWorkflow(id); diff --git a/src/Conductor/Startup.cs b/src/Conductor/Startup.cs index c1cd6c6..8b9250b 100644 --- a/src/Conductor/Startup.cs +++ b/src/Conductor/Startup.cs @@ -25,6 +25,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using System.Security.Cryptography; using Microsoft.IdentityModel.Tokens; +using Conductor.Auth; namespace Conductor { diff --git a/src/Conductor/appsettings.json b/src/Conductor/appsettings.json index f4c503c..6853c74 100644 --- a/src/Conductor/appsettings.json +++ b/src/Conductor/appsettings.json @@ -8,5 +8,5 @@ "DbConnectionString": "mongodb://localhost:27017/", "DbName": "conductor", "AuthEnabled": false, - "IssuerKey": "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw==" + "AuthPublicKey": "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw==" } From 18116f4664f4d83ddfd846ab3d4225fbcf92a162 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Mon, 23 Dec 2019 08:14:50 -0800 Subject: [PATCH 06/12] wip --- src/Conductor/Auth/AuthExtensions.cs | 25 +++++++--------------- src/Conductor/appsettings.json | 2 +- tests/ScratchPad/Program.cs | 31 +++++++++++++++------------- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/Conductor/Auth/AuthExtensions.cs b/src/Conductor/Auth/AuthExtensions.cs index da268e8..836b3f8 100644 --- a/src/Conductor/Auth/AuthExtensions.cs +++ b/src/Conductor/Auth/AuthExtensions.cs @@ -21,33 +21,24 @@ public static class AuthExtensions { public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builder, IConfiguration config) { - builder.AddJwtBearer(options => - { - var publicKey = Convert.FromBase64String(config.GetValue("AuthPublicKey")); - var e1 = ECDsa.Create(); - e1.ImportParameters(new ECParameters() - { - Curve = ECCurve.NamedCurves.nistP256, - Q = new ECPoint() - { - X = publicKey.Take(32).ToArray(), - Y = publicKey.Skip(32).Take(32).ToArray() - } - }); + var publicKey = Convert.FromBase64String(config.GetValue("AuthPublicKey")); + var e1 = ECDsa.Create(); + e1.ImportSubjectPublicKeyInfo(publicKey, out int br); + var signingKey = new ECDsaSecurityKey(e1); + builder.AddJwtBearer(options => + { options.IncludeErrorDetails = true; + options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, - IssuerSigningKey = new ECDsaSecurityKey(e1), + IssuerSigningKey = signingKey, ValidateIssuer = false, ValidateAudience = false }; - options.RequireHttpsMetadata = false; - options.SaveToken = true; - options.Validate(); }); diff --git a/src/Conductor/appsettings.json b/src/Conductor/appsettings.json index 6853c74..f629e7e 100644 --- a/src/Conductor/appsettings.json +++ b/src/Conductor/appsettings.json @@ -8,5 +8,5 @@ "DbConnectionString": "mongodb://localhost:27017/", "DbName": "conductor", "AuthEnabled": false, - "AuthPublicKey": "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw==" + "AuthPublicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==" } diff --git a/tests/ScratchPad/Program.cs b/tests/ScratchPad/Program.cs index 2c1d445..c184b28 100644 --- a/tests/ScratchPad/Program.cs +++ b/tests/ScratchPad/Program.cs @@ -24,16 +24,17 @@ class Program { static string PrivateKey = "MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="; //static string PublicKey = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBpZkp9ShVxbEFtRo4r09XBEvQA/0pf26sHGKnbAxUS+G+enGBiuNdxRTzUUEvE8KK4AQxyCUotbaAydfVs4S18="; - static string RealPublicKey = "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="; + //static string RealPublicKey = "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="; + static string RealPublicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg=="; static string Key2 = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G"; static string Key2Pub = "hcEuk1hs8QsZT24s96dnlORoFLF+Alh1wxVkKdSs0mH8CM7SEAOhONKi8xM1/kEDufovcKwvvxx+z3r1SvNpGA=="; static void Main(string[] args) { - MakeToken(); - //var token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJmaXJzdE5hbWUiOiJ0ZXN0IiwibGFzdE5hbWUiOiJ0ZXN0IiwibmJmIjoxNTc2NDQyODg5LCJleHAiOjE1NzY3MDIwODksImlhdCI6MTU3NjQ0Mjg4OX0.VK05y04Uxjsev8_kxOyk5UtkWJTwBWnJ47_riM5EwYEibJiusfbI_34PD2AM4iYiP_5RxkUIc6TiG2LG2FdbWg"; - //Console.WriteLine(VerifyToken(token)); + //MakeToken(); + var token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA"; + Console.WriteLine(VerifyToken(token)); } static void MakeToken() @@ -93,15 +94,16 @@ public static bool VerifyToken(string jwt) //var keyDataStr = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBpZkp9ShVxbEFtRo4r09XBEvQA/0pf26sHGKnbAxUS+G+enGBiuNdxRTzUUEvE8KK4AQxyCUotbaAydfVs4S18="; //File.ReadAllText(@"C:\dev\jwt\test1.key.pub"); var pubKey = Convert.FromBase64String(RealPublicKey); //(RealPublicKey); var e1 = ECDsa.Create(); - e1.ImportParameters(new ECParameters() - { - Curve = ECCurve.NamedCurves.nistP256, - Q = new ECPoint() - { - X = pubKey.Take(32).ToArray(), - Y = pubKey.Skip(32).Take(32).ToArray() - } - }); + e1.ImportSubjectPublicKeyInfo(pubKey, out int br); + //e1.ImportParameters(new ECParameters() + //{ + // Curve = ECCurve.NamedCurves.nistP256, + // Q = new ECPoint() + // { + // X = pubKey.Take(32).ToArray(), + // Y = pubKey.Skip(32).Take(32).ToArray() + // } + //}); var key = new ECDsaSecurityKey(e1); var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); @@ -113,7 +115,8 @@ public static bool VerifyToken(string jwt) { IssuerSigningKey = key, ValidateAudience = false, - ValidateIssuer = false + ValidateIssuer = false, + ValidateLifetime = false }; var cp = tokenHandler.ValidateToken(jwt, tvp, out var vt); From c7117e756e6d06a294b6af30974d2e5207987b82 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Mon, 23 Dec 2019 10:01:28 -0800 Subject: [PATCH 07/12] wip --- src/Conductor/Auth/AuthExtensions.cs | 11 ++++++----- tests/ScratchPad/Program.cs | 19 +++++++++++-------- tests/ScratchPad/jwt.json | 8 ++++++++ 3 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 tests/ScratchPad/jwt.json diff --git a/src/Conductor/Auth/AuthExtensions.cs b/src/Conductor/Auth/AuthExtensions.cs index 836b3f8..1e1c77a 100644 --- a/src/Conductor/Auth/AuthExtensions.cs +++ b/src/Conductor/Auth/AuthExtensions.cs @@ -36,7 +36,8 @@ public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builde ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = false, - ValidateAudience = false + ValidateAudience = false, + RequireExpirationTime = false }; options.Validate(); @@ -55,10 +56,10 @@ public static AuthenticationBuilder AddBypassAuth(this AuthenticationBuilder bui { Subject = new ClaimsIdentity(new[] { - new Claim( ClaimTypes.Role, Roles.Admin), - new Claim( ClaimTypes.Role, Roles.Author), - new Claim( ClaimTypes.Role, Roles.Controller), - new Claim( ClaimTypes.Role, Roles.Viewer), + new Claim(ClaimTypes.Role, Roles.Admin), + new Claim(ClaimTypes.Role, Roles.Author), + new Claim(ClaimTypes.Role, Roles.Controller), + new Claim(ClaimTypes.Role, Roles.Viewer), }), SigningCredentials = sc, }; diff --git a/tests/ScratchPad/Program.cs b/tests/ScratchPad/Program.cs index c184b28..449716d 100644 --- a/tests/ScratchPad/Program.cs +++ b/tests/ScratchPad/Program.cs @@ -33,7 +33,7 @@ class Program static void Main(string[] args) { //MakeToken(); - var token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA"; + var token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjpbIkFkbWluIiwiQXV0aG9yIiwiQ29udHJvbGxlciIsIlZpZXdlciJdLCJleHAiOjQ3MzI3OTY2NDQsImlhdCI6MTU3NzEyMzA0NH0.CQB0QCLgBxWPqlq48tMzR8eyNbguTkpQK4n9GL6ynzM-SNL9sxO7zTPwbDEXTIJYQc2nk0VemE2FlYO057DV1A"; Console.WriteLine(VerifyToken(token)); } @@ -65,11 +65,13 @@ static void MakeToken() { Subject = new ClaimsIdentity(new[] { - new Claim( "email", "test@test.com" ), - new Claim( "firstName", "test" ), - new Claim( "lastName", "test" ) - }), - Expires = now.AddYears(3), + new Claim(ClaimTypes.Role, "Admin"), + new Claim(ClaimTypes.Role, "Author"), + new Claim(ClaimTypes.Role, "Controller"), + new Claim(ClaimTypes.Role, "Viewer"), + }), + + Expires = now.AddYears(100), SigningCredentials = sc, }; @@ -106,14 +108,15 @@ public static bool VerifyToken(string jwt) //}); var key = new ECDsaSecurityKey(e1); - var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); + //var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); - var token = tokenHandler.ReadJwtToken(jwt); + //var token = tokenHandler.ReadJwtToken(jwt); var tvp = new TokenValidationParameters() { IssuerSigningKey = key, + ValidateIssuerSigningKey = true, ValidateAudience = false, ValidateIssuer = false, ValidateLifetime = false diff --git a/tests/ScratchPad/jwt.json b/tests/ScratchPad/jwt.json new file mode 100644 index 0000000..ba73490 --- /dev/null +++ b/tests/ScratchPad/jwt.json @@ -0,0 +1,8 @@ +{ + "role": [ + "Admin", + "Author", + "Controller", + "Viewer" + ] +} \ No newline at end of file From 7c78f34c8a67e0ccdc9c5af369727b234efa6e04 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Mon, 23 Dec 2019 17:06:04 -0800 Subject: [PATCH 08/12] wip --- docs/auth.md | 29 +++++++++++++++ src/Conductor/Auth/AuthExtensions.cs | 36 +++++++++++++++---- src/Conductor/Auth/Policies.cs | 15 ++++++++ src/Conductor/Auth/Roles.cs | 17 --------- src/Conductor/Conductor.csproj | 1 + .../Controllers/DefinitionController.cs | 8 ++--- src/Conductor/Controllers/EventController.cs | 4 +-- src/Conductor/Controllers/InfoController.cs | 2 +- src/Conductor/Controllers/StepController.cs | 4 +-- .../Controllers/WorkflowController.cs | 10 +++--- src/Conductor/Startup.cs | 11 +++++- src/Conductor/appsettings.json | 4 +-- tests/ScratchPad/Program.cs | 19 +++++----- tests/ScratchPad/ScratchPad.csproj | 1 + tests/ScratchPad/grant-aoth0.json | 15 ++++++++ tests/ScratchPad/jwt.json | 2 +- 16 files changed, 124 insertions(+), 54 deletions(-) create mode 100644 docs/auth.md create mode 100644 src/Conductor/Auth/Policies.cs delete mode 100644 src/Conductor/Auth/Roles.cs create mode 100644 tests/ScratchPad/grant-aoth0.json diff --git a/docs/auth.md b/docs/auth.md new file mode 100644 index 0000000..8881f11 --- /dev/null +++ b/docs/auth.md @@ -0,0 +1,29 @@ +# Authentication + +By default, authentication is disabled. To enable it, set the `AUTH` environment variable to `true` and the `PUBLICKEY` variable to a valid [ECDsa](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) public key. + +If authentication is enabled then you need to include a signed [JWT bearer token](https://jwt.io/) along with every request. The is done by adding the `Authorization: Bearer <>` header to each request. +The token should be a valid JWT token that was signed with the corresponding private key to the public one in the environment variable. + +The token must also include role claims that indicate the level of access. The following roles are used within Conductor. +* `Admin` - Adminstrative tasks. +* `Author` - Authoring of workflow definitions and steps. +* `Controller` - Starting, stopping, suspending and resuming workflows. +* `Viewer` - Querying the status of a workflow. + +A minimal JWT payload the include all the roles would look as follows + +```json +{ + "role": [ + "Admin", + "Author", + "Controller", + "Viewer" + ] +} +``` + +https://github.com/keycloak/keycloak +https://www.keycloak.org/ +https://hub.docker.com/r/jboss/keycloak \ No newline at end of file diff --git a/src/Conductor/Auth/AuthExtensions.cs b/src/Conductor/Auth/AuthExtensions.cs index 1e1c77a..0374c24 100644 --- a/src/Conductor/Auth/AuthExtensions.cs +++ b/src/Conductor/Auth/AuthExtensions.cs @@ -21,13 +21,16 @@ public static class AuthExtensions { public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builder, IConfiguration config) { - var publicKey = Convert.FromBase64String(config.GetValue("AuthPublicKey")); + var publicKeyBase64 = Environment.GetEnvironmentVariable("PUBLICKEY"); + if (string.IsNullOrEmpty(publicKeyBase64)) + publicKeyBase64 = config.GetValue("AuthPublicKey"); + var publicKey = Convert.FromBase64String(publicKeyBase64); var e1 = ECDsa.Create(); e1.ImportSubjectPublicKeyInfo(publicKey, out int br); var signingKey = new ECDsaSecurityKey(e1); builder.AddJwtBearer(options => - { + { options.IncludeErrorDetails = true; options.RequireHttpsMetadata = false; @@ -37,7 +40,15 @@ public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builde IssuerSigningKey = signingKey, ValidateIssuer = false, ValidateAudience = false, - RequireExpirationTime = false + RequireExpirationTime = false + }; + options.Events = new JwtBearerEvents() + { + OnTokenValidated = context => + { + + return Task.CompletedTask; + } }; options.Validate(); @@ -56,10 +67,10 @@ public static AuthenticationBuilder AddBypassAuth(this AuthenticationBuilder bui { Subject = new ClaimsIdentity(new[] { - new Claim(ClaimTypes.Role, Roles.Admin), - new Claim(ClaimTypes.Role, Roles.Author), - new Claim(ClaimTypes.Role, Roles.Controller), - new Claim(ClaimTypes.Role, Roles.Viewer), + new Claim(ClaimTypes.Role, Policies.Admin), + new Claim(ClaimTypes.Role, Policies.Author), + new Claim(ClaimTypes.Role, Policies.Controller), + new Claim(ClaimTypes.Role, Policies.Viewer), }), SigningCredentials = sc, }; @@ -91,6 +102,17 @@ public static AuthenticationBuilder AddBypassAuth(this AuthenticationBuilder bui return builder; } + + public static void AddPolicies(this IServiceCollection services) + { + services.AddAuthorization(options => + { + options.AddPolicy(Policies.Admin, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains("admin")))); + options.AddPolicy(Policies.Author, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains("author")))); + options.AddPolicy(Policies.Controller, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains("controller")))); + options.AddPolicy(Policies.Viewer, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains("viewer")))); + }); + } } } diff --git a/src/Conductor/Auth/Policies.cs b/src/Conductor/Auth/Policies.cs new file mode 100644 index 0000000..1f0029d --- /dev/null +++ b/src/Conductor/Auth/Policies.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Conductor.Auth +{ + public static class Policies + { + public const string Admin = "admin"; + public const string Viewer = "viewer"; + public const string Controller = "controller"; + public const string Author = "author"; + } +} diff --git a/src/Conductor/Auth/Roles.cs b/src/Conductor/Auth/Roles.cs deleted file mode 100644 index e7552ce..0000000 --- a/src/Conductor/Auth/Roles.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Conductor.Auth -{ - public static class Roles - { - public const string Admin = "Admin"; - public const string Viewer = "Viewer"; - public const string Controller = "Controller"; - public const string Author = "Author"; - - public const string ControllerOrViewer = "Controller,Viewer"; - } -} diff --git a/src/Conductor/Conductor.csproj b/src/Conductor/Conductor.csproj index 538881e..7e45c07 100644 --- a/src/Conductor/Conductor.csproj +++ b/src/Conductor/Conductor.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Conductor/Controllers/DefinitionController.cs b/src/Conductor/Controllers/DefinitionController.cs index b6eb7e6..1445dc8 100644 --- a/src/Conductor/Controllers/DefinitionController.cs +++ b/src/Conductor/Controllers/DefinitionController.cs @@ -23,14 +23,14 @@ public DefinitionController(IDefinitionService service) } [HttpGet] - [Authorize(Roles = Roles.Author)] + [Authorize(Policy = Policies.Author)] public ActionResult> Get() { return new string[] { "value1", "value2" }; } [HttpGet("{id}")] - [Authorize(Roles = Roles.Author)] + [Authorize(Policy = Policies.Author)] public ActionResult Get(string id) { var result = _service.GetDefinition(id); @@ -42,7 +42,7 @@ public ActionResult Get(string id) } [HttpPost] - [Authorize(Roles = Roles.Author)] + [Authorize(Policy = Policies.Author)] public void Post([FromBody] Definition value) { _service.RegisterNewDefinition(value); @@ -57,7 +57,7 @@ public void Post([FromBody] Definition value) // DELETE api/values/5 [HttpDelete("{id}")] - [Authorize(Roles = Roles.Author)] + [Authorize(Policy = Policies.Author)] public void Delete(int id) { } diff --git a/src/Conductor/Controllers/EventController.cs b/src/Conductor/Controllers/EventController.cs index a0a8f5c..aa5ec52 100644 --- a/src/Conductor/Controllers/EventController.cs +++ b/src/Conductor/Controllers/EventController.cs @@ -25,14 +25,12 @@ public EventController(IWorkflowController workflowController) _workflowController = workflowController; } - [Authorize(Roles = Roles.Controller)] + [Authorize(Policy = Policies.Controller)] [HttpPost("{name}/{key}")] public async Task Post(string name, string key, [FromBody] object data) { await _workflowController.PublishEvent(name, key, data); Response.StatusCode = 204; } - - } } \ No newline at end of file diff --git a/src/Conductor/Controllers/InfoController.cs b/src/Conductor/Controllers/InfoController.cs index c10fabe..4534ec1 100644 --- a/src/Conductor/Controllers/InfoController.cs +++ b/src/Conductor/Controllers/InfoController.cs @@ -11,7 +11,7 @@ namespace Conductor.Controllers { [Route("api/[controller]")] - [ApiController] + [ApiController] public class InfoController : ControllerBase { [HttpGet] diff --git a/src/Conductor/Controllers/StepController.cs b/src/Conductor/Controllers/StepController.cs index 8509fc3..05c7e57 100644 --- a/src/Conductor/Controllers/StepController.cs +++ b/src/Conductor/Controllers/StepController.cs @@ -27,7 +27,7 @@ public StepController(ICustomStepService service) [HttpGet("{name}")] - [Authorize(Roles = Roles.Author)] + [Authorize(Policy = Policies.Author)] public IActionResult Get(string name) { var resource = _service.GetStepResource(name); @@ -41,7 +41,7 @@ public IActionResult Get(string name) } [HttpPost("{name}")] - [Authorize(Roles = Roles.Author)] + [Authorize(Policy = Policies.Author)] public async void Post(string name) { using (var sr = new StreamReader(Request.Body)) diff --git a/src/Conductor/Controllers/WorkflowController.cs b/src/Conductor/Controllers/WorkflowController.cs index 98a9427..d056d5b 100644 --- a/src/Conductor/Controllers/WorkflowController.cs +++ b/src/Conductor/Controllers/WorkflowController.cs @@ -33,7 +33,7 @@ public WorkflowController(IWorkflowController workflowController, IPersistencePr } [HttpGet("{id}")] - [Authorize(Roles = Roles.ControllerOrViewer)] + [Authorize(Policy = Policies.Viewer)] public async Task> Get(string id) { var result = await _persistenceProvider.GetWorkflowInstance(id); @@ -44,7 +44,7 @@ public async Task> Get(string id) } [HttpPost("{id}")] - [Authorize(Roles = Roles.Controller)] + [Authorize(Policy = Policies.Controller)] public async Task> Post(string id, [FromBody] ExpandoObject data) { var instanceId = await _workflowController.StartWorkflow(id, data); @@ -54,7 +54,7 @@ public async Task> Post(string id, [FromBody] Exp } [HttpPut("{id}/suspend")] - [Authorize(Roles = Roles.Controller)] + [Authorize(Policy = Policies.Controller)] public async Task Suspend(string id) { var result = await _workflowController.SuspendWorkflow(id); @@ -65,7 +65,7 @@ public async Task Suspend(string id) } [HttpPut("{id}/resume")] - [Authorize(Roles = Roles.Controller)] + [Authorize(Policy = Policies.Controller)] public async Task Resume(string id) { var result = await _workflowController.ResumeWorkflow(id); @@ -76,7 +76,7 @@ public async Task Resume(string id) } [HttpDelete("{id}")] - [Authorize(Roles = Roles.Controller)] + [Authorize(Policy = Policies.Controller)] public async Task Terminate(string id) { var result = await _workflowController.TerminateWorkflow(id); diff --git a/src/Conductor/Startup.cs b/src/Conductor/Startup.cs index 8b9250b..36e333f 100644 --- a/src/Conductor/Startup.cs +++ b/src/Conductor/Startup.cs @@ -49,6 +49,14 @@ public void ConfigureServices(IServiceCollection services) if (string.IsNullOrEmpty(redisConnectionStr)) redisConnectionStr = Configuration.GetValue("RedisConnectionString"); + var authEnabled = false; + var authEnabledStr = Environment.GetEnvironmentVariable("AUTH"); + if (string.IsNullOrEmpty(authEnabledStr)) + authEnabled = Configuration.GetValue("AuthEnabled"); + else + authEnabled = Convert.ToBoolean(authEnabledStr); + + services.AddMvc(options => { options.InputFormatters.Add(new YamlRequestBodyInputFormatter()); @@ -64,11 +72,12 @@ public void ConfigureServices(IServiceCollection services) options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }); - if (Configuration.GetValue("AuthEnabled")) + if (authEnabled) authConfig.AddJwtAuth(Configuration); else authConfig.AddBypassAuth(); + services.AddPolicies(); services.AddWorkflow(cfg => { diff --git a/src/Conductor/appsettings.json b/src/Conductor/appsettings.json index f629e7e..a35506b 100644 --- a/src/Conductor/appsettings.json +++ b/src/Conductor/appsettings.json @@ -7,6 +7,6 @@ "AllowedHosts": "*", "DbConnectionString": "mongodb://localhost:27017/", "DbName": "conductor", - "AuthEnabled": false, - "AuthPublicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==" + "AuthEnabled": true, + "AuthPublicKey": "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEIOD1lD7PkLyHGj3n/+d564tc5s4eqrox5OinvTL5mekSR1GFTSpEvOELYWLqSfADkRNgDuR0g9cBVmaNtFwiIA==" } diff --git a/tests/ScratchPad/Program.cs b/tests/ScratchPad/Program.cs index 449716d..ec231d0 100644 --- a/tests/ScratchPad/Program.cs +++ b/tests/ScratchPad/Program.cs @@ -22,9 +22,7 @@ namespace ScratchPad { class Program { - static string PrivateKey = "MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="; - //static string PublicKey = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBpZkp9ShVxbEFtRo4r09XBEvQA/0pf26sHGKnbAxUS+G+enGBiuNdxRTzUUEvE8KK4AQxyCUotbaAydfVs4S18="; - //static string RealPublicKey = "GlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK413FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw=="; + static string PrivateKey = "MHQCAQEEIA2OjSVFJwR/tsoo0VtrgAfUXu+lRXRXOA10eS/UF5tloAcGBSuBBAAKoUQDQgAEIOD1lD7PkLyHGj3n/+d564tc5s4eqrox5OinvTL5mekSR1GFTSpEvOELYWLqSfADkRNgDuR0g9cBVmaNtFwiIA=="; static string RealPublicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg=="; static string Key2 = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G"; @@ -32,9 +30,9 @@ class Program static void Main(string[] args) { - //MakeToken(); - var token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjpbIkFkbWluIiwiQXV0aG9yIiwiQ29udHJvbGxlciIsIlZpZXdlciJdLCJleHAiOjQ3MzI3OTY2NDQsImlhdCI6MTU3NzEyMzA0NH0.CQB0QCLgBxWPqlq48tMzR8eyNbguTkpQK4n9GL6ynzM-SNL9sxO7zTPwbDEXTIJYQc2nk0VemE2FlYO057DV1A"; - Console.WriteLine(VerifyToken(token)); + MakeToken(); + //var token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjpbIkFkbWluIiwiQXV0aG9yIiwiQ29udHJvbGxlciIsIlZpZXdlciJdLCJleHAiOjQ3MzI3OTY2NDQsImlhdCI6MTU3NzEyMzA0NH0.CQB0QCLgBxWPqlq48tMzR8eyNbguTkpQK4n9GL6ynzM-SNL9sxO7zTPwbDEXTIJYQc2nk0VemE2FlYO057DV1A"; + //Console.WriteLine(VerifyToken(token)); } static void MakeToken() @@ -61,14 +59,13 @@ static void MakeToken() var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); + var tokenDescriptor = new SecurityTokenDescriptor { + Subject = new ClaimsIdentity(new[] { - new Claim(ClaimTypes.Role, "Admin"), - new Claim(ClaimTypes.Role, "Author"), - new Claim(ClaimTypes.Role, "Controller"), - new Claim(ClaimTypes.Role, "Viewer"), + new Claim("scope", "admin author viewer") }), Expires = now.AddYears(100), @@ -109,7 +106,7 @@ public static bool VerifyToken(string jwt) var key = new ECDsaSecurityKey(e1); //var sc = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256); - + //SecurityAlgorithms. var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); //var token = tokenHandler.ReadJwtToken(jwt); diff --git a/tests/ScratchPad/ScratchPad.csproj b/tests/ScratchPad/ScratchPad.csproj index e8b5582..378a417 100644 --- a/tests/ScratchPad/ScratchPad.csproj +++ b/tests/ScratchPad/ScratchPad.csproj @@ -8,6 +8,7 @@ + diff --git a/tests/ScratchPad/grant-aoth0.json b/tests/ScratchPad/grant-aoth0.json new file mode 100644 index 0000000..626943c --- /dev/null +++ b/tests/ScratchPad/grant-aoth0.json @@ -0,0 +1,15 @@ +{ + "iss": "https://dev-u0y0i8nq.auth0.com/", + "sub": "amTIIg9IHQILWVn4wUJqrJPpg3kEqDiV@clients", + "aud": "Test", + "iat": 1577146113, + "exp": 1577232513, + "azp": "amTIIg9IHQILWVn4wUJqrJPpg3kEqDiV", + "scope": "scope1 test:dostuff wtf", + "gty": "client-credentials", + "permissions": [ + "scope1", + "test:dostuff", + "wtf" + ] +} \ No newline at end of file diff --git a/tests/ScratchPad/jwt.json b/tests/ScratchPad/jwt.json index ba73490..efa707d 100644 --- a/tests/ScratchPad/jwt.json +++ b/tests/ScratchPad/jwt.json @@ -5,4 +5,4 @@ "Controller", "Viewer" ] -} \ No newline at end of file +} From a819235d10a1de0b3af2fd759394d7853f7e39d9 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Mon, 23 Dec 2019 20:41:11 -0800 Subject: [PATCH 09/12] wip --- docs/auth.md | 35 ++++++++------- mkdocs.yml | 1 + src/Conductor/Auth/AuthExtensions.cs | 65 ++++++++++++++++++---------- src/Conductor/Auth/Permissions.cs | 15 +++++++ src/Conductor/Startup.cs | 2 +- src/Conductor/appsettings.json | 7 ++- 6 files changed, 83 insertions(+), 42 deletions(-) create mode 100644 src/Conductor/Auth/Permissions.cs diff --git a/docs/auth.md b/docs/auth.md index 8881f11..f7577f8 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -1,29 +1,32 @@ # Authentication -By default, authentication is disabled. To enable it, set the `AUTH` environment variable to `true` and the `PUBLICKEY` variable to a valid [ECDsa](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) public key. +Conductor supports integration authentication using the [OpenID Connect](https://openid.net/connect/) protocol. + +By default, authentication is disabled. To enable it, +* Set the `AUTH` environment variable to `true` +* Set the `ALG` environment variable to the signing algorithm (`RS256` or `ES256`) +* Set the `PUBLICKEY` variable to a Base64 encoded public key. If authentication is enabled then you need to include a signed [JWT bearer token](https://jwt.io/) along with every request. The is done by adding the `Authorization: Bearer <>` header to each request. The token should be a valid JWT token that was signed with the corresponding private key to the public one in the environment variable. -The token must also include role claims that indicate the level of access. The following roles are used within Conductor. -* `Admin` - Adminstrative tasks. -* `Author` - Authoring of workflow definitions and steps. -* `Controller` - Starting, stopping, suspending and resuming workflows. -* `Viewer` - Querying the status of a workflow. +The token must also include a scope claim that indicate the level of access. The following scopes are used within Conductor. + +* `conductor:admin` - Adminstrative tasks. +* `conductor:author` - Authoring of workflow definitions and steps. +* `conductor:controller` - Starting, stopping, suspending and resuming workflows. +* `conductor:viewer` - Querying the status of a workflow. -A minimal JWT payload the include all the roles would look as follows +A minimal JWT payload the include all the scopes would look as follows ```json { - "role": [ - "Admin", - "Author", - "Controller", - "Viewer" - ] + "scope": "conductor:admin conductor:author conductor:controller conductor:viewer" } ``` -https://github.com/keycloak/keycloak -https://www.keycloak.org/ -https://hub.docker.com/r/jboss/keycloak \ No newline at end of file +Some authentication servers that support [OpenID Connect](https://openid.net/connect/) include + +* [Auth0](https://auth0.com/) - A cloud service +* [Keycloak](https://github.com/keycloak/keycloak/) - Open source auth server + diff --git a/mkdocs.yml b/mkdocs.yml index 28c8f6c..efc9a8a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -5,5 +5,6 @@ nav: - Custom Steps: custom-steps.md - Http Requests: http-walkthru.md - API Reference: api-reference.md + - Authentication: auth.md - Roadmap: roadmap.md theme: readthedocs \ No newline at end of file diff --git a/src/Conductor/Auth/AuthExtensions.cs b/src/Conductor/Auth/AuthExtensions.cs index 0374c24..45e15ef 100644 --- a/src/Conductor/Auth/AuthExtensions.cs +++ b/src/Conductor/Auth/AuthExtensions.cs @@ -19,15 +19,10 @@ namespace Conductor.Auth { public static class AuthExtensions { + public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builder, IConfiguration config) { - var publicKeyBase64 = Environment.GetEnvironmentVariable("PUBLICKEY"); - if (string.IsNullOrEmpty(publicKeyBase64)) - publicKeyBase64 = config.GetValue("AuthPublicKey"); - var publicKey = Convert.FromBase64String(publicKeyBase64); - var e1 = ECDsa.Create(); - e1.ImportSubjectPublicKeyInfo(publicKey, out int br); - var signingKey = new ECDsaSecurityKey(e1); + var signingKey = LoadKey(config); builder.AddJwtBearer(options => { @@ -42,14 +37,6 @@ public static AuthenticationBuilder AddJwtAuth(this AuthenticationBuilder builde ValidateAudience = false, RequireExpirationTime = false }; - options.Events = new JwtBearerEvents() - { - OnTokenValidated = context => - { - - return Task.CompletedTask; - } - }; options.Validate(); }); @@ -67,10 +54,7 @@ public static AuthenticationBuilder AddBypassAuth(this AuthenticationBuilder bui { Subject = new ClaimsIdentity(new[] { - new Claim(ClaimTypes.Role, Policies.Admin), - new Claim(ClaimTypes.Role, Policies.Author), - new Claim(ClaimTypes.Role, Policies.Controller), - new Claim(ClaimTypes.Role, Policies.Viewer), + new Claim("scope", $"{Permissions.Admin} {Permissions.Author} {Permissions.Controller} {Permissions.Viewer}") }), SigningCredentials = sc, }; @@ -107,12 +91,47 @@ public static void AddPolicies(this IServiceCollection services) { services.AddAuthorization(options => { - options.AddPolicy(Policies.Admin, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains("admin")))); - options.AddPolicy(Policies.Author, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains("author")))); - options.AddPolicy(Policies.Controller, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains("controller")))); - options.AddPolicy(Policies.Viewer, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains("viewer")))); + options.AddPolicy(Policies.Admin, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains(Permissions.Admin)))); + options.AddPolicy(Policies.Author, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains(Permissions.Author)))); + options.AddPolicy(Policies.Controller, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains(Permissions.Controller)))); + options.AddPolicy(Policies.Viewer, policy => policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type == "scope" && x.Value.Split(' ').Contains(Permissions.Viewer)))); }); } + + private static SecurityKey LoadKey(IConfiguration config) + { + var publicKeyBase64 = Environment.GetEnvironmentVariable("PUBLICKEY"); + if (string.IsNullOrEmpty(publicKeyBase64)) + publicKeyBase64 = config.GetSection("Auth").GetValue("PublicKey"); + var publicKey = Convert.FromBase64String(publicKeyBase64); + + var algName = Environment.GetEnvironmentVariable("ALG"); + if (string.IsNullOrEmpty(algName)) + algName = config.GetSection("Auth").GetValue("Algorithm"); + + if (algName.StartsWith("RS")) + { + var rsa = RSA.Create(); + try + { + rsa.ImportSubjectPublicKeyInfo(publicKey, out _); + } + catch + { + rsa.ImportRSAPublicKey(publicKey, out _); + } + return new RsaSecurityKey(rsa); + } + + if (algName.StartsWith("ES")) + { + var e1 = ECDsa.Create(); + e1.ImportSubjectPublicKeyInfo(publicKey, out _); + return new ECDsaSecurityKey(e1); + } + + throw new ArgumentException("Only RSA and ECDSA algorithms are supported"); + } } } diff --git a/src/Conductor/Auth/Permissions.cs b/src/Conductor/Auth/Permissions.cs new file mode 100644 index 0000000..7f99aaf --- /dev/null +++ b/src/Conductor/Auth/Permissions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Conductor.Auth +{ + public static class Permissions + { + public const string Admin = "conductor:admin"; + public const string Viewer = "conductor:viewer"; + public const string Controller = "conductor:controller"; + public const string Author = "conductor:author"; + } +} diff --git a/src/Conductor/Startup.cs b/src/Conductor/Startup.cs index 36e333f..0dde63f 100644 --- a/src/Conductor/Startup.cs +++ b/src/Conductor/Startup.cs @@ -52,7 +52,7 @@ public void ConfigureServices(IServiceCollection services) var authEnabled = false; var authEnabledStr = Environment.GetEnvironmentVariable("AUTH"); if (string.IsNullOrEmpty(authEnabledStr)) - authEnabled = Configuration.GetValue("AuthEnabled"); + authEnabled = Configuration.GetSection("Auth").GetValue("Enabled"); else authEnabled = Convert.ToBoolean(authEnabledStr); diff --git a/src/Conductor/appsettings.json b/src/Conductor/appsettings.json index a35506b..b3dca0d 100644 --- a/src/Conductor/appsettings.json +++ b/src/Conductor/appsettings.json @@ -7,6 +7,9 @@ "AllowedHosts": "*", "DbConnectionString": "mongodb://localhost:27017/", "DbName": "conductor", - "AuthEnabled": true, - "AuthPublicKey": "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEIOD1lD7PkLyHGj3n/+d564tc5s4eqrox5OinvTL5mekSR1GFTSpEvOELYWLqSfADkRNgDuR0g9cBVmaNtFwiIA==" + "Auth": { + "Enabled": false, + "Algorithm": "RS256", + "PublicKey": "MIIBCgKCAQEAoXomNzza8XWtCU6OtOTSQoiW+rdTDwsOpJrbawnyFHck5VfsWv060WbFuTV0fH958bFupkfUIjMQFluQnYR3RteP1MSSjUpz/GQp/yYs3kbPOA2wMvFTUTmlyor6k6hgFpOEUNeJFNpvkZNAu61ww8sur3om+qbE7EwfIKDCiSPlJ5AER0BOedNOTtmLl36mktMDJGcfbCKenUIxDxqD+Wgm2oLwW21GUrnle7HzEE1HFeH2R1iBLpqflWGYoz4LSAFHzerqwZ4S7HaPJ/RMLe4SuU74P7VKP7vdn0ZUXleu10Lqi5xwp+k7iCJjKviREUvtsEUPhOkYz6SWBAfSsQIDAQAB" + } } From dc2b4f32594f1037113e06ad50edb6fb0eb54b94 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Mon, 23 Dec 2019 20:43:14 -0800 Subject: [PATCH 10/12] typo --- docs/auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/auth.md b/docs/auth.md index f7577f8..6372b8b 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -1,6 +1,6 @@ # Authentication -Conductor supports integration authentication using the [OpenID Connect](https://openid.net/connect/) protocol. +Conductor supports integrated authentication using the [OpenID Connect](https://openid.net/connect/) protocol. By default, authentication is disabled. To enable it, * Set the `AUTH` environment variable to `true` From a1c7d6213077ce96189809c56938a907b1868485 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Tue, 24 Dec 2019 07:03:40 -0800 Subject: [PATCH 11/12] wip --- docs/auth.md | 6 +++--- docs/installation.md | 8 ++++---- readme.md | 8 ++++---- src/Conductor/Auth/AuthExtensions.cs | 4 ++-- src/Conductor/EnvironmentVariables.cs | 16 ++++++++++++++++ src/Conductor/Startup.cs | 6 +++--- .../docker-compose.yml | 8 ++++---- 7 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 src/Conductor/EnvironmentVariables.cs diff --git a/docs/auth.md b/docs/auth.md index 6372b8b..e4d3124 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -3,9 +3,9 @@ Conductor supports integrated authentication using the [OpenID Connect](https://openid.net/connect/) protocol. By default, authentication is disabled. To enable it, -* Set the `AUTH` environment variable to `true` -* Set the `ALG` environment variable to the signing algorithm (`RS256` or `ES256`) -* Set the `PUBLICKEY` variable to a Base64 encoded public key. +* Set the `auth` environment variable to `true` +* Set the `alg` environment variable to the signing algorithm (`RS256` or `ES256`) +* Set the `publickey` variable to a Base64 encoded public key. If authentication is enabled then you need to include a signed [JWT bearer token](https://jwt.io/) along with every request. The is done by adding the `Authorization: Bearer <>` header to each request. The token should be a valid JWT token that was signed with the corresponding private key to the public one in the environment variable. diff --git a/docs/installation.md b/docs/installation.md index 6758dc7..d1be0b0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -7,7 +7,7 @@ Conductor uses MongoDB as it's datastore, you will also need an instance of Mong Use this command to start a container (with the API available on port 5001) that points to `mongodb://my-mongo-server:27017/` as it's datastore. ``` -$ docker run -p 127.0.0.1:5001:80/tcp --env DBHOST=mongodb://my-mongo-server:27017/ danielgerlag/conductor +$ docker run -p 127.0.0.1:5001:80/tcp --env dbhost=mongodb://my-mongo-server:27017/ danielgerlag/conductor ``` If you wish to run a fleet of Conductor nodes, then you also need to have a Redis instance, which they will use as a backplane. This is not required if you are only running one instance. @@ -18,8 +18,8 @@ Simply have all your conductor instances point to the same MongoDB and Redis ins You can configure the database and Redis backplane by setting environment variables. ``` -DBHOST: <> -REDIS: <> (optional) +dbhost: <> +redis: <> (optional) ``` If you would like to setup a conductor container (API on port 5001) and a MongoDB container at the same time and have them linked, use this docker compose file: @@ -34,7 +34,7 @@ services: links: - mongo environment: - DBHOST: mongodb://mongo:27017/ + dbhost: mongodb://mongo:27017/ mongo: image: mongo ``` diff --git a/readme.md b/readme.md index 7a52de1..5844d14 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ Conductor uses MongoDB as it's datastore, you will also need an instance of Mong Use this command to start a container (with the API available on port 5001) that points to `mongodb://my-mongo-server:27017/` as it's datastore. ```bash -$ docker run -p 127.0.0.1:5001:80/tcp --env DBHOST=mongodb://my-mongo-server:27017/ danielgerlag/conductor +$ docker run -p 127.0.0.1:5001:80/tcp --env dbhost=mongodb://my-mongo-server:27017/ danielgerlag/conductor ``` If you wish to run a fleet of Conductor nodes, then you also need to have a Redis instance, which they will use as a backplane. This is not required if you are only running one instance. @@ -23,8 +23,8 @@ Simply have all your conductor instances point to the same MongoDB and Redis ins You can configure the database and Redis backplane by setting environment variables. ``` -DBHOST: <> -REDIS: <> (optional) +dbhost: <> +redis: <> (optional) ``` If you would like to setup a conductor container (API on port 5001) and a MongoDB container at the same time and have them linked, use this docker compose file: @@ -39,7 +39,7 @@ services: links: - mongo environment: - DBHOST: mongodb://mongo:27017/ + dbhost: mongodb://mongo:27017/ mongo: image: mongo ``` diff --git a/src/Conductor/Auth/AuthExtensions.cs b/src/Conductor/Auth/AuthExtensions.cs index 45e15ef..940afec 100644 --- a/src/Conductor/Auth/AuthExtensions.cs +++ b/src/Conductor/Auth/AuthExtensions.cs @@ -100,12 +100,12 @@ public static void AddPolicies(this IServiceCollection services) private static SecurityKey LoadKey(IConfiguration config) { - var publicKeyBase64 = Environment.GetEnvironmentVariable("PUBLICKEY"); + var publicKeyBase64 = EnvironmentVariables.PublicKey; if (string.IsNullOrEmpty(publicKeyBase64)) publicKeyBase64 = config.GetSection("Auth").GetValue("PublicKey"); var publicKey = Convert.FromBase64String(publicKeyBase64); - var algName = Environment.GetEnvironmentVariable("ALG"); + var algName = EnvironmentVariables.Alg; if (string.IsNullOrEmpty(algName)) algName = config.GetSection("Auth").GetValue("Algorithm"); diff --git a/src/Conductor/EnvironmentVariables.cs b/src/Conductor/EnvironmentVariables.cs new file mode 100644 index 0000000..48d8b2d --- /dev/null +++ b/src/Conductor/EnvironmentVariables.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Conductor +{ + public static class EnvironmentVariables + { + public static string DbHost => Environment.GetEnvironmentVariable("dbhost"); + public static string Redis => Environment.GetEnvironmentVariable("redis"); + public static string Auth => Environment.GetEnvironmentVariable("auth"); + public static string PublicKey => Environment.GetEnvironmentVariable("publickey"); + public static string Alg => Environment.GetEnvironmentVariable("alg"); + } +} diff --git a/src/Conductor/Startup.cs b/src/Conductor/Startup.cs index 0dde63f..473d02b 100644 --- a/src/Conductor/Startup.cs +++ b/src/Conductor/Startup.cs @@ -41,16 +41,16 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { - var dbConnectionStr = Environment.GetEnvironmentVariable("DBHOST"); + var dbConnectionStr = EnvironmentVariables.DbHost; if (string.IsNullOrEmpty(dbConnectionStr)) dbConnectionStr = Configuration.GetValue("DbConnectionString"); - var redisConnectionStr = Environment.GetEnvironmentVariable("REDIS"); + var redisConnectionStr = EnvironmentVariables.Redis; if (string.IsNullOrEmpty(redisConnectionStr)) redisConnectionStr = Configuration.GetValue("RedisConnectionString"); var authEnabled = false; - var authEnabledStr = Environment.GetEnvironmentVariable("AUTH"); + var authEnabledStr = EnvironmentVariables.Auth; if (string.IsNullOrEmpty(authEnabledStr)) authEnabled = Configuration.GetSection("Auth").GetValue("Enabled"); else diff --git a/tests/Conductor.IntegrationTests/docker-compose.yml b/tests/Conductor.IntegrationTests/docker-compose.yml index 2c93002..1af3a9b 100644 --- a/tests/Conductor.IntegrationTests/docker-compose.yml +++ b/tests/Conductor.IntegrationTests/docker-compose.yml @@ -9,8 +9,8 @@ services: - mongo - redis environment: - DBHOST: mongodb://mongo:27017/ - REDIS: redis:6379 + dbhost: mongodb://mongo:27017/ + redis: redis:6379 conductor2: build: context: ../../ @@ -20,8 +20,8 @@ services: - mongo - redis environment: - DBHOST: mongodb://mongo:27017/ - REDIS: redis:6379 + dbhost: mongodb://mongo:27017/ + redis: redis:6379 mongo: image: mongo redis: From 74ff2d221f0544109e10c59dcbb13eb32f4392a3 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Thu, 26 Dec 2019 07:58:14 -0800 Subject: [PATCH 12/12] wip --- docs/auth.md | 5 ++++- temp/jwtRS256.key | 5 ----- temp/jwtRS256.key.pub | 1 - temp/test1.key | 5 ----- temp/test1.key.pub | 1 - temp/test1.pem | 0 temp/test2.key | 5 ----- temp/test2.key.pub | 1 - 8 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 temp/jwtRS256.key delete mode 100644 temp/jwtRS256.key.pub delete mode 100644 temp/test1.key delete mode 100644 temp/test1.key.pub delete mode 100644 temp/test1.pem delete mode 100644 temp/test2.key delete mode 100644 temp/test2.key.pub diff --git a/docs/auth.md b/docs/auth.md index e4d3124..2e2aabd 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -3,7 +3,7 @@ Conductor supports integrated authentication using the [OpenID Connect](https://openid.net/connect/) protocol. By default, authentication is disabled. To enable it, -* Set the `auth` environment variable to `true` +* Set the `auth` environment variable to `'true'` * Set the `alg` environment variable to the signing algorithm (`RS256` or `ES256`) * Set the `publickey` variable to a Base64 encoded public key. @@ -28,5 +28,8 @@ A minimal JWT payload the include all the scopes would look as follows Some authentication servers that support [OpenID Connect](https://openid.net/connect/) include * [Auth0](https://auth0.com/) - A cloud service +* [Okta](https://www.okta.com/) - A cloud service * [Keycloak](https://github.com/keycloak/keycloak/) - Open source auth server +* [Identity Server](https://identityserver.io/) - Open source auth server +* [Dex](https://github.com/dexidp/dex) - Open source auth server diff --git a/temp/jwtRS256.key b/temp/jwtRS256.key deleted file mode 100644 index d6302d6..0000000 --- a/temp/jwtRS256.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIGIxetO+Ve9EQY5tIA7uJJkyb+yjjeJpOhWDaBGlvAw/oAoGCCqGSM49 -AwEHoUQDQgAEp6ZTrRNJ1VS550siDtCE2BOTMThRqndZAiNIWUshMNYQTs6jaJGv -6Bl0R29Phy5bpmqCgNelDPOhA2NU8oiETw== ------END EC PRIVATE KEY----- diff --git a/temp/jwtRS256.key.pub b/temp/jwtRS256.key.pub deleted file mode 100644 index 8fd0ef8..0000000 --- a/temp/jwtRS256.key.pub +++ /dev/null @@ -1 +0,0 @@ -ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKemU60TSdVUuedLIg7QhNgTkzE4Uap3WQIjSFlLITDWEE7Oo2iRr+gZdEdvT4cuW6ZqgoDXpQzzoQNjVPKIhE8= danie@daniel-desktop diff --git a/temp/test1.key b/temp/test1.key deleted file mode 100644 index ddc15c3..0000000 --- a/temp/test1.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIEVB7uPYNa0BSvKQPhXVPf0cVilo88STthQrwzIEHnfSoAoGCCqGSM49 -AwEHoUQDQgAEGlmSn1KFXFsQW1GjivT1cES9AD/Sl/bqwcYqdsDFRL4b56cYGK41 -3FFPNRQS8TworgBDHIJSi1toDJ19WzhLXw== ------END EC PRIVATE KEY----- diff --git a/temp/test1.key.pub b/temp/test1.key.pub deleted file mode 100644 index e38b025..0000000 --- a/temp/test1.key.pub +++ /dev/null @@ -1 +0,0 @@ -ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBpZkp9ShVxbEFtRo4r09XBEvQA/0pf26sHGKnbAxUS+G+enGBiuNdxRTzUUEvE8KK4AQxyCUotbaAydfVs4S18= daniel@daniel-desktop diff --git a/temp/test1.pem b/temp/test1.pem deleted file mode 100644 index e69de29..0000000 diff --git a/temp/test2.key b/temp/test2.key deleted file mode 100644 index 262e644..0000000 --- a/temp/test2.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEILZmFcSjtm9SZ0EI+pgFYxvwLt5Cr9twko1+KwJNdjBJoAoGCCqGSM49 -AwEHoUQDQgAEhcEuk1hs8QsZT24s96dnlORoFLF+Alh1wxVkKdSs0mH8CM7SEAOh -ONKi8xM1/kEDufovcKwvvxx+z3r1SvNpGA== ------END EC PRIVATE KEY----- diff --git a/temp/test2.key.pub b/temp/test2.key.pub deleted file mode 100644 index e934be6..0000000 --- a/temp/test2.key.pub +++ /dev/null @@ -1 +0,0 @@ -ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIXBLpNYbPELGU9uLPenZ5TkaBSxfgJYdcMVZCnUrNJh/AjO0hADoTjSovMTNf5BA7n6L3CsL78cfs969UrzaRg= daniel@daniel-desktop