Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update To Support v5 API #48

Merged
merged 9 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
104 changes: 83 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
# Conjur API for .NET

This is an implementation of the .NET API for [Conjur](https://developer.conjur.net/).
This is a *Draft* implementation of the .NET API for [V5 Conjur](https://developer.conjur.net/).
This implementation includes an example that shows how to:

- Authenticate
- Load Policy
- Check permissions to get the value of a variable
- Get the value of a variable
- Use a Host Factory token to create a new Host and get an apiKey to use with Conjur

## Building

This sample was built and tested with Visual Studio 2015.
This sample was built and tested with Visual Studio 2015.

To load in Visual Studio, from the Visual Studio File menu select Open > Project/Solution > api-dotnet.sln and build the solution. This will create:

Expand All @@ -20,39 +21,107 @@ To load in Visual Studio, from the Visual Studio File menu select Open > Project

Optionally, to build in a Docker container, it is recommended to use Mono and xbuild.

## Methods

### `Client`

#### `Client Client(uri, account)`
- Create new Conjur instance
- `uri` - URI of the Conjur server. Example: `https://myconjur.org.com/api`
- `account` - Name of the Conjur account

#### `void client.LogIn(string userName, string password)`
- Login to a Conjur user
- `userName` - Username of Conjur user to login as
- `password` - Password of user

#### `void client.TrustedCertificates.ImportPem (string certPath)`
- Add Conjur root certificate to system trust store
- `certPath` = Path to cert

#### `client.Credential = new NetworkCredential(string userName, string apiKey)`
- To login with an API key, use it directly
- `userName` - Username of user to login as
- `apiKey` - API key of user/host/etc

#### `IEnumerable<Variable> client.ListVariables(string query = null)`
- Returns a list of variable objects
- `query` - Additional query parameters (not required)

#### `uint client.CountVariables(string query = null)`
- Return count of Conjur variables conforming to the `query` parameter
- `query` - Additional query parameters (not required)

#### `Host client.CreateHost(string name, string hostFactoryToken)`
- Creates a host using a host factory token
- `name` - Name of the host to create
- `hostFactoryToken` - Host factory token

### `Policy`

#### `Policy client.Policy(string policyName)`
- Create a Conjur policy object
- `policyName` - Name of policy

#### `policy.LoadPolicy(Stream policyContent)`
- Load policy into Conjur
- `policyContent` - The policy

### `Variable`

#### `Variable client.Variable(string name)`
- Instantiate a Variable object
- `name` - Name of the variable

#### `Boolean variable.Check(string privilege)`
- Check if the current entity has the specified privilege on this variable
- `privilege` - string name of the privilege to check for
- Privileges: read, create, update, delete, execute

#### `void variable.AddSecret(bytes val)`
- Change current variable to val
- `val` - Value in bytes to update current variable to

#### `String variable.GetValue()`
- Return the value of the current Variable
JakeQuilty marked this conversation as resolved.
Show resolved Hide resolved

## Usage

To run the sample in Visual Studio, set the `example` project as the Startup Project. To do so, in the Solution Explorer right click over `example` and select `Set as Startup Project`.
To run the sample in Visual Studio, set the `example` project as the Startup Project. To do so, in the Solution Explorer right click over `example` and select `Set as Startup Project`.

```sh
Usage: Example <applianceURL>
<applianceCertificatePath>
<username>
<password>
<accountName>
<username>
<password>
<variableId>
<hostFactoryToken>
```

applianceURL: the applianceURL including /api e.g. https://conjurmaster.myorg.com/api
`applianceURL`: the applianceURL e.g. `https://conjurmaster.myorg.com/`

`applianceCertificatePath`: the path and name of the Conjur appliance certificate. The easiest way to get the certifiate is to use the Conjur CLI command `conjur init -u conjurmaster.myorg.com -f .conjurrc`. The certificate can be taken from any system you have run the Conjur CLI from.

applianceCertificatePath: the path and name of the Conjur appliance certificate. The easiest way to get the certifiate is to use the Conjur CLI command `conjur init -h conjurmaster.myorg.com -f .conjurrc`. The certificate can be taken from any system you have run the Conjur CLI from.
`accountName`: The name of the account in Conjur.

username: Username of a user in Conjur. Alternatively can be a hostname.
`username`: Username of a user in Conjur. Alternatively can be a hostname.

password: Password of a user in Conjur. Alternatively can be a host apiKey.
`password`: Password of a user in Conjur. Alternatively can be a host apiKey.

variableId: The name of an existing variable in Conjur that has a value set and for which the `username` has execute permissions.
`variableId`: The name of an existing variable in Conjur that has a value set and for which the `username` has execute permissions.

hostFactoryToken: A hostfactory token. The easiest way to get a host factory token for testing is to add a hostfactory to a layer using the Conjur CLI command `conjur hostfactory create` and `conjur hostfactory token create`. Take the token returned from that call and pass it as the hostFactoryToken parameter to this example.
`hostFactoryToken`: A host factory token. The easiest way to get a host factory token for testing is to add a hostfactory to a layer using the Conjur CLI command `conjur hostfactory create` and `conjur hostfactory token create`. Take the token returned from that call and pass it as the hostFactoryToken parameter to this example.

## Example

```sh
// Instantiate a Conjur Client object.
// parameter: URI - conjur appliance URI (including /api)
// parameter: URI - conjur appliance URI
// parameter: ACCOUNT - conjur account name
// return: Client object - if URI is incorrect errors thrown when used
Uri uri = new Uri("https://myorg.com/api");
Client conjurClient = new Client(uri);
Uri uri = new Uri("https://myorg.com");
Client conjurClient = new Client(uri, account);

// Login with Conjur credentials like userid and password,
// or hostid and api_key, etc
Expand Down Expand Up @@ -82,13 +151,6 @@ hostFactoryToken: A hostfactory token. The easiest way to get a host factory tok
}
```

## Contributing

We welcome contributions of all kinds to this repository. For instructions on how to get started and descriptions of our development workflows, please see our [contributing
guide][contrib].

[contrib]: https://github.com/cyberark/conjur-api-dotnet/blob/master/CONTRIBUTING.md

## License

This repository is licensed under Apache License 2.0 - see [`LICENSE`](LICENSE) for more details.
JakeQuilty marked this conversation as resolved.
Show resolved Hide resolved
89 changes: 89 additions & 0 deletions conjur-api/ApiConfigurationManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// <copyright file="ApiConfigurationManager.cs" company="CyberArk Software Ltd.">
// Copyright (c) 2020 CyberArk Software Ltd. All rights reserved.
// </copyright>
// <summary>
// Configuration class for this API project
// </summary>
using System;
using System.Configuration;

namespace Conjur
{
public sealed class ApiConfigurationManager
{
public const string HTTP_REQUEST_TIMEOUT = "HTTP_REQUEST_TIMEOUT";
public const string TOKEN_REFRESH_TIMEOUT = "TOKEN_REFRESH_TIMEOUT";

private ApiConfigurationManager()
{
}

public static ApiConfigurationManager GetInstance()
{
return Nested.Instance;
}

private object locker = new object();

private int? httpRequestTimeout = null;

/// <summary>
/// Gets/Sets the global http request timeout configuration.
/// Request timeout configuration in milliseconds, default is: 100000.
/// </summary>
public int HttpRequestTimeout {
get {
if (httpRequestTimeout == null) {
lock (locker) {
if (httpRequestTimeout == null) {
string value = ConfigurationManager.AppSettings.Get (HTTP_REQUEST_TIMEOUT);
httpRequestTimeout = 100000; //100 seconds; WebRequest default timeout
if (!string.IsNullOrWhiteSpace (value)) {
httpRequestTimeout = Convert.ToInt32 (value);
}
}
}
}
return httpRequestTimeout.Value;
}
set {
httpRequestTimeout = value;
}
}

private uint? tokenRefreshTimeout = null;

/// <summary>
/// Gets/Sets the global token refresh timeout configuration.
/// Token refresh timeout configuration in milliseconds, default is: 420000 (7 minutes).
/// </summary>
public uint TokenRefreshTimeout {
get {
if (tokenRefreshTimeout == null) {
lock (locker) {
if (tokenRefreshTimeout == null) {
string value = ConfigurationManager.AppSettings.Get (TOKEN_REFRESH_TIMEOUT);
tokenRefreshTimeout = 420000; //7 minutes
if (!string.IsNullOrWhiteSpace (value)) {
tokenRefreshTimeout = Convert.ToUInt32 (value);
}
}
}
}
return tokenRefreshTimeout.Value;
}
set {
tokenRefreshTimeout = value;
}
}

private class Nested
{
internal static readonly ApiConfigurationManager Instance = new ApiConfigurationManager();

static Nested()
{
}
}
}
}
92 changes: 64 additions & 28 deletions conjur-api/ApiKeyAuthenticator.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
// <copyright file="ApiKeyAuthenticator.cs" company="Conjur Inc.">
// Copyright (c) 2016 Conjur Inc. All rights reserved.
// <copyright file="ApiKeyAuthenticator.cs" company="CyberArk Software Ltd.">
// Copyright (c) 2020 CyberArk Software Ltd. All rights reserved.
// </copyright>
// <summary>
// API key authenticator.
// </summary>

using System;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;

namespace Conjur
{
using System;
using System.IO;
using System.Net;
using System.Threading;

/// <summary>
/// API key authenticator.
/// </summary>
public class ApiKeyAuthenticator : IAuthenticator
{
private readonly Uri uri;
private readonly NetworkCredential credential;
private readonly object locker = new object();

private string token;
private Timer timer;
private string token = null;
private Timer timer = null;

/// <summary>
/// Initializes a new instance of the <see cref="Conjur.ApiKeyAuthenticator"/> class.
/// </summary>
/// <param name="authnUri">Authentication base URI, for example
/// <param name="authnUri">Authentication base URI, for example
/// "https://example.com/api/authn".</param>
/// <param name="credential">User name and API key to use, where
/// <param name="account">The name of the Conjur organization account.</param>
/// <param name="credential">User name and API key to use, where
/// username is for example "bob" or "host/jenkins".</param>
public ApiKeyAuthenticator(Uri authnUri, NetworkCredential credential)
public ApiKeyAuthenticator(Uri authnUri, string account, NetworkCredential credential)
{
this.credential = credential;
this.uri = new Uri(authnUri + "/users/"
+ WebUtility.UrlEncode(credential.UserName)
+ "/authenticate");
this.timer = new Timer((_) => Interlocked.Exchange(ref this.token, null));
this.uri = new Uri($"{authnUri}/{Uri.EscapeDataString(account)}/{Uri.EscapeDataString(credential.UserName)}/authenticate");
sgnn7 marked this conversation as resolved.
Show resolved Hide resolved
}

#region IAuthenticator implementation
Expand All @@ -48,29 +48,65 @@ public ApiKeyAuthenticator(Uri authnUri, NetworkCredential credential)
/// It needs to be base64-encoded to be used in a web request.</returns>
public string GetToken()
{
var token = this.token;
string token = this.token;
if (token != null)
{
return token;
}

lock(timer) if (this.token == null) {
var request = WebRequest.Create(this.uri);
request.Method = "POST";

using (var writer = new StreamWriter(request.GetRequestStream()))
writer.Write(this.credential.Password);
lock (this.locker)
sgnn7 marked this conversation as resolved.
Show resolved Hide resolved
{
if (this.token == null)
{
HttpWebRequest request = WebRequest.CreateHttp(this.uri);
request.Timeout = ApiConfigurationManager.GetInstance().HttpRequestTimeout;
request.Method = WebRequestMethods.Http.Post;
request.ContentLength = credential.SecurePassword.Length;
request.AllowWriteStreamBuffering = false;

Interlocked.Exchange(ref this.token, request.Read());
this.StartTokenTimer(new TimeSpan(0, 7, 30));
IntPtr bstr = IntPtr.Zero;
byte[] bArr = new byte[credential.SecurePassword.Length];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code here should have a comment about what it does

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add - cleaning secrets from memory footprint :)

try
{
bstr = Marshal.SecureStringToBSTR(credential.SecurePassword);
for (int i = 0; i < credential.SecurePassword.Length; i++)
{
bArr[i] = Marshal.ReadByte(bstr, i * 2);
}
using (Stream stream = request.GetRequestStream())
{
stream.Write(bArr, 0, bArr.Length);
Interlocked.Exchange(ref this.token, request.Read());
this.StartTokenTimer(TimeSpan.FromMilliseconds(ApiConfigurationManager.GetInstance().TokenRefreshTimeout));
}
}
finally
{
if (bstr != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(bstr);
}
Array.Clear(bArr, 0, bArr.Length);
}
}
}

return this.token;
}

#endregion

internal void StartTokenTimer(TimeSpan timeout)
{
this.timer.Change(timeout, Timeout.InfiniteTimeSpan);
this.timer = new Timer(this.TimerCallback, null, timeout, Timeout.InfiniteTimeSpan);
}

private void TimerCallback(object state)
{
// timer is disposable resource but there is no way to dispose it from outside
// so each time when token expires we dispose it
// it will allow garbage collection of unecessary client and authentificator classes
this.timer.Dispose();
this.timer = null;
Interlocked.Exchange(ref this.token, null);
}
}
}
Loading