Skip to content

Instantly share code, notes, and snippets.

@pksorensen
Created March 17, 2016 06:08
Show Gist options
  • Select an option

  • Save pksorensen/44f9cedb1307b29d41b1 to your computer and use it in GitHub Desktop.

Select an option

Save pksorensen/44f9cedb1307b29d41b1 to your computer and use it in GitHub Desktop.
namespace SInnovations.VSTeamServices.TasksBuilder.KeyVault.ResourceTypes
{
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using CommandLine;
using SInnovations.VSTeamServices.TasksBuilder.Attributes;
using SInnovations.VSTeamServices.TasksBuilder.ResourceTypes;
/// <summary>
/// Parsing options for <see cref="KeyVaultOutput"/> that is used to generate input options in vsts task.
/// </summary>
public class KeyVaultOptions
{
private readonly KeyVaultOutput options;
public KeyVaultOptions(KeyVaultOutput options){
this.options = options;
}
[ArmResourceIdPicker("Microsoft.KeyVault/vaults", "2015-06-01")]
[Display(Description = "The keyvault namespace", Name = "KeyVault Name")]
[Required]
[Option("KeyVaultName")]
public string VaultName { get { return options.VaultName; } set { options.VaultName = value.StartsWith("/subscriptions") ? value.Split('/').Last() :value; } }
[ArmResourceProviderPicker("$(KeyVaultName)","secrets", "2015-06-01")]
[Display(Description = "The keyvault secret name to store value in", Name = "Secret Name")]
[Required]
[Option("SecretName")]
public string SecretName { get { return options.SecretName; } set { options.SecretName = value.StartsWith("/subscriptions")? value.Substring(value.IndexOf("secrets/")+ "secrets/".Length).Split('/').First():value; } }
[Display(ResourceType = typeof(Tags),
Description = "Tags, seperate tags with comma and key:value with semicolon.",
Name = "Secret Tags",
ShortName = "KeyVaultSecretTags")]
public Dictionary<string, string> Tags { get; set; }
}
}
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using CommandLine;
using Newtonsoft.Json.Linq;
using SInnovations.DnsMadeEasy;
using SInnovations.LetsEncrypt.DnsMadeEasyManager;
using SInnovations.LetsEncrypt.Services.Defaults;
using SInnovations.LetsEncrypt.Stores.Defaults;
using SInnovations.VSTeamServices.TasksBuilder.Attributes;
using SInnovations.VSTeamServices.TasksBuilder.AzureResourceManager.ResourceTypes;
using SInnovations.VSTeamServices.TasksBuilder.ConsoleUtils;
using SInnovations.VSTeamServices.TasksBuilder.KeyVault.ResourceTypes;
using SInnovations.VSTeamServices.TasksBuilder.ResourceTypes;
[assembly: AssemblyInformationalVersion("1.0.10")]
namespace SInnovations.LetsEncrypt.VSTSTask
{
public class DnsMadeEasyOptions
{
private DnsMadeEasyResource DnsMadeEasyClientCredetials { get; set; }
public DnsMadeEasyOptions(DnsMadeEasyResource resouce)
{
DnsMadeEasyClientCredetials = resouce;
}
[Option("DnsMadeEasyUsername", HelpText = "PrincipalKey")]
public string ApiKey
{
get { return DnsMadeEasyClientCredetials.ApiKey; }
set { DnsMadeEasyClientCredetials.ApiKey = value; }
}
[Option("DnsMadeEasyPassword", HelpText = "PrincipalId")]
public string ApiSecret
{
get { return DnsMadeEasyClientCredetials.ApiSecret; }
set { DnsMadeEasyClientCredetials.ApiSecret = value; }
}
}
[ResourceType(TaskInputType = "connectedService:Generic")]
public class DnsMadeEasyResource : DnsMadeEasyClientCredetials, IConsoleReader
{
public void OnConsoleParsing(Parser parser, string[] args, object options, PropertyInfo info)
{
var dnsMadeEasyOptions = new DnsMadeEasyOptions(this);
var disp = info.GetCustomAttribute<DisplayAttribute>().ShortName;
parser.ParseArguments(args, dnsMadeEasyOptions);
}
}
[EntryPoint("Creating Certificate $(hostingPlanName)")]
[Group(DisplayName = "Add keys to KeyVault", Name = "KeyVault", isExpanded = false)]
public class ProgramOptions
{
public ProgramOptions()
{
KeyVault = new KeyVaultOutput<ProgramOptions>(k => k.ConnectedServiceName);
}
[Display(Name ="DnsMadeEasy", ShortName = "DnsMadeEasy", Description ="The DnsMadeEasy connection", ResourceType = typeof(DnsMadeEasyResource))]
public DnsMadeEasyClientCredetials DnsMadeEasyConnection { get; set; }
[Option("DomainDNSIdentifier")]
public string DomainDNSIdentifier { get; set; }
[Option("LetsEncryptBaseUri", HelpText = "The BaseUri used for lets encrypt api.", DefaultValue = "https://acme-staging.api.letsencrypt.org/")]
public string LetsEncryptBaseUri { get; set; }
[Option("SignerEmail", HelpText = "The signer email")]
public string SignerEmail { get; set; }
[Option("PfxPassword", HelpText = "The password for the generated pfx file")]
public string PfxPassword { get; set; }
[Display(ResourceType = typeof(GlobPath))]
[Option("OutputPath", HelpText = "Filesystem output to store the pfx file")]
public string OutputPath { get; set; }
[Display(Name = "Service Principal", GroupName = "KeyVault", ShortName = "ConnectedServiceName", ResourceType = typeof(ServiceEndpoint), Description = "Azure Service Principal used to communicate with keyvault")]
public ServiceEndpoint ConnectedServiceName { get; set; }
[ConnectedServiceRelation(typeof(ConnectedServiceRelation))]
[Display(GroupName = "KeyVault", ResourceType = typeof(KeyVaultOutput<ProgramOptions>))]
public KeyVaultOutput<ProgramOptions> KeyVault { get; set; }
public class ConnectedServiceRelation : PropertyRelation<ProgramOptions, ServiceEndpoint>
{
public ConnectedServiceRelation()
: base(@class => @class.ConnectedServiceName)
{
}
}
}
class Program
{
static void Main(string[] args)
{
#if LOCAL
var creds = JObject.Parse(File.ReadAllText(@"C:\dev\dnscred"));
args = args.LoadFrom<ProgramOptions>(@"c:\dev\credsSinno.txt")
.LoadFrom<ProgramOptions>(null,
o => o.SignerEmail ?? "pks@s-innovations.net",
o => o.PfxPassword ?? "1234562",
o => o.OutputPath ?? @"c:\dev\test.pfx",
o => o.DomainDNSIdentifier ?? "earthml.xyz"
).Concat(new[] {
"--DnsMadeEasyUsername",creds["apiKey"].ToString(),
"--DnsMadeEasyPassword",creds["apiSecret"].ToString(),
}).ToArray();
//args = new[] { "--build" };
#endif
var options = ConsoleHelper.ParseAndHandleArguments<ProgramOptions>("Create LetsEncrypt Certificate", args);
LetsEncryptService letsEncrypt = CreateInMemoryService(
letsEncryptOptions: new LetsEncryptServiceOptions
{
BaseUri = options.LetsEncryptBaseUri
},
dnsCredentials: options.DnsMadeEasyConnection);
var cert = letsEncrypt.GenerateCertificateAsync(
new GenerateCertificateRequestOptions
{
DnsIdentifier = options.DomainDNSIdentifier,
SignerEmail = options.SignerEmail,
PfxPassword = options.PfxPassword
}).GetAwaiter().GetResult();
X509Certificate2 X509Certificate = FixFriendlyName(options, ref cert);
if (!string.IsNullOrEmpty(options.OutputPath))
{
File.WriteAllBytes(options.OutputPath, cert);
}
if (options.KeyVault.IsPresent())
{
var certBase64 = Convert.ToBase64String(cert);
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes(new JObject(
new JProperty("data", certBase64),
new JProperty("dataType", "pfx"),
new JProperty("password", options.PfxPassword)
).ToString()));
var tags = options.KeyVault.Tags;
tags.Add("letsEncryptDnsIdentifier", options.DomainDNSIdentifier);
tags.Add("letsEncryptChallenge", "dns");
tags.Add("thumbprint", X509Certificate.Thumbprint);
tags.Add("friendlyName", X509Certificate.FriendlyName);
options.KeyVault.SetSecret(value, tags, contentType: "application/x-pkcs12");
}
}
private static X509Certificate2 FixFriendlyName(ProgramOptions options, ref byte[] cert)
{
var X509Certificate = new X509Certificate2(cert, options.PfxPassword, X509KeyStorageFlags.Exportable);
X509Certificate.FriendlyName = "CN=" + options.DomainDNSIdentifier;
cert = X509Certificate.Export(X509ContentType.Pkcs12, options.PfxPassword);
return X509Certificate;
}
private static LetsEncryptService CreateInMemoryService(LetsEncryptServiceOptions letsEncryptOptions, DnsMadeEasyClientCredetials dnsCredentials)
{
var signerService = new DefaultRS256SignerService(new InMemoryRS256SignerStore());
var acmeService = new DefaultAcmeClientService(signerService, new InMemoryAcmeRegistrationStore());
var challengeService = new DefaultDnsChallengeService(new LetsEncryptDnsMadeEasyManager(dnsCredentials));
var letsEncrypt = new LetsEncryptService(
letsEncryptOptions,
acmeService,
challengeService);
return letsEncrypt;
}
}
}
{
"id": "53df2e5d-57a6-434c-92ef-431a4257bf4e",
"name": "LetsEncryptCertificateTask",
"friendlyName": "LetsEncrypt Certificate Generation",
"description": "Creates a LetsEncrypt Certificate using DNS Challenge. Currently only dnsmadeeasy is supported",
"category": "Utility",
"visibility": [
"Build",
"Release"
],
"demands": [
"azureps"
],
"author": "S-Innovations v/Poul Kjeldager Sørensen",
"version": {
"major": 1,
"minor": 0,
"patch": 10
},
"minimumAgentVersion": "1.92.0",
"groups": [
{
"name": "KeyVault",
"displayName": "Add keys to KeyVault",
"isExpanded": false
}
],
"inputs": [
{
"name": "DnsMadeEasy",
"type": "connectedService:Generic",
"label": "DnsMadeEasy",
"defaultValue": "",
"required": false,
"helpMarkDown": "The DnsMadeEasy connection",
"properties": {
"EditableOptions": "True"
}
},
{
"name": "DomainDNSIdentifier",
"type": "string",
"label": "DomainDNSIdentifier",
"defaultValue": "",
"required": false,
"properties": {
"EditableOptions": "True"
}
},
{
"name": "LetsEncryptBaseUri",
"type": "string",
"label": "LetsEncryptBaseUri",
"defaultValue": "https://acme-staging.api.letsencrypt.org/",
"required": false,
"helpMarkDown": "The BaseUri used for lets encrypt api.",
"properties": {
"EditableOptions": "True"
}
},
{
"name": "SignerEmail",
"type": "string",
"label": "SignerEmail",
"defaultValue": "",
"required": false,
"helpMarkDown": "The signer email",
"properties": {
"EditableOptions": "True"
}
},
{
"name": "PfxPassword",
"type": "string",
"label": "PfxPassword",
"defaultValue": "",
"required": false,
"helpMarkDown": "The password for the generated pfx file",
"properties": {
"EditableOptions": "True"
}
},
{
"name": "OutputPath",
"type": "filePath",
"label": "OutputPath",
"defaultValue": "",
"required": false,
"helpMarkDown": "Filesystem output to store the pfx file",
"properties": {
"EditableOptions": "True"
}
},
{
"name": "ConnectedServiceName",
"type": "connectedService:AzureRM",
"label": "Service Principal",
"defaultValue": "",
"required": false,
"groupName": "KeyVault",
"helpMarkDown": "Azure Service Principal used to communicate with keyvault",
"properties": {
"EditableOptions": "True"
}
},
{
"name": "KeyVaultName",
"type": "pickList",
"label": "KeyVault Name",
"defaultValue": "",
"required": true,
"groupName": "KeyVault",
"helpMarkDown": "The keyvault namespace",
"properties": {
"EditableOptions": "True"
}
},
{
"name": "SecretName",
"type": "pickList",
"label": "Secret Name",
"defaultValue": "",
"required": true,
"groupName": "KeyVault",
"helpMarkDown": "The keyvault secret name to store value in",
"properties": {
"EditableOptions": "True"
}
},
{
"name": "KeyVaultSecretTags",
"type": "string",
"label": "Secret Tags",
"defaultValue": "",
"required": false,
"groupName": "KeyVault",
"helpMarkDown": "Tags, seperate tags with comma and key:value with semicolon.",
"properties": {
"EditableOptions": "True"
}
}
],
"instanceNameFormat": "Creating Certificate $(hostingPlanName)",
"execution": {
"PowerShell": {
"target": "$(currentDirectory)\\OauthBroker.ps1",
"workingDirectory": "$(currentDirectory)",
"argumentFormat": ""
}
},
"sourceDefinitions": [
{
"endpoint": "https://management.azure.com/subscriptions/$(authKey.SubscriptionId)/providers/Microsoft.KeyVault/vaults?api-version=2015-06-01",
"target": "KeyVaultName",
"authKey": "$(ConnectedServiceName)",
"selector": "jsonpath:$.value[*].name",
"keySelector": "jsonpath:$.value[*].id"
},
{
"endpoint": "https://management.azure.com/$(KeyVaultName)/secrets?api-version=2015-06-01",
"target": "SecretName",
"authKey": "$(ConnectedServiceName)",
"selector": "jsonpath:$.value[*].name",
"keySelector": "jsonpath:$.value[*].id"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment