Skip to content

Conversation

@tmolbergen
Copy link

@tmolbergen tmolbergen commented Dec 15, 2024

Description

Add argument option to load ldapusername/ldappassword from environment variable (operator define the env variable name to pull from) or a operator defineable json file. I've specifically left out any configuration options or similar as it probably goes to much in conflict with the enterprise version of Sharphound

Motivation and Context

I am not a big fan of spraying passwords in my windows logs. This should hopefully to some extent mitigate that. Do note that this is not to be looked at as a security feature, but it atleast prevents the host from spraying passwords into logs and collected logs.

How Has This Been Tested?

Compiled code, tested with json file and envoptions (also tested the --ldapusername --ldappassword options).
This was tested on 4 different active directory domains. The changes are not huge and didnt impact any of the operations of Sharphound.

Screenshots (if appropriate):

Types of changes

  • Chore (a change that does not modify the application functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • [X ] New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Documentation updates are needed, and have been made accordingly.
  • I have added and/or updated tests to cover my changes.
  • All new and existing tests passed.
  • My changes include a database migration.

Didnt know there were any tests or where to define them. Im not doing anything towards the database, so that shouldnt be needed to define. Not sure if there are any other places than the readme to update

Im by no means of the imagination a developer, but it would be nice to have this or some similar feature included to ensure that passwords are not sprayed in the logs.

Summary by CodeRabbit

  • New Features

    • Three new CLI arguments for LDAP authentication: load credentials from a JSON file, or reference environment variables for username and password separately.
    • Enhanced credential handling with validation to ensure all required components are provided.
  • Documentation

    • Updated README documenting the new CLI credential options.

@ddlees
Copy link
Member

ddlees commented Jan 8, 2025

recheck

@github-actions
Copy link

github-actions bot commented Jan 8, 2025

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@tmolbergen
Copy link
Author

I have read the CLA Document and I hereby sign the CLA

@tmolbergen
Copy link
Author

recheck

@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

Walkthrough

The changes add three credential supply methods for LDAP authentication: JSON file path, environment variables, and legacy username/password. A new Credentials class enables JSON deserialization, three new options configure credential sources, and credential handling logic implements precedence ordering with validation.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added CLI argument documentation for three new LDAP credential options: --ldapenvusername, --ldapenvpassword, and --ldapcredentialsjsonpath.
Data Model
src/JsonExtensions.cs
Introduced new public Credentials class with Username and Password auto-properties for JSON credential deserialization.
Configuration
src/Options.cs
Added three new public string properties: LDAPCredentialJsonPath, LDAPEnvUsername, and LDAPEnvPassword with corresponding help text.
Credential Handling
src/Sharphound.cs
Implemented credential resolution logic with three sources (JSON file, environment variables, legacy username/password) prioritized in order. Includes validation: errors if LDAPEnvUsername lacks LDAPEnvPassword.

Sequence Diagram

sequenceDiagram
    actor User
    participant App as Sharphound
    participant JSON as JSON File
    participant Env as Environment
    participant LDAP as LDAP Connection

    User->>App: Start with credential options
    
    alt LDAPCredentialJsonPath provided
        App->>JSON: Read JSON file
        JSON-->>App: Credentials object
        App->>LDAP: Set username/password
    else LDAPEnvUsername provided
        App->>Env: Check LDAPEnvUsername
        Env-->>App: Username value
        alt LDAPEnvPassword exists
            App->>Env: Check LDAPEnvPassword
            Env-->>App: Password value
            App->>LDAP: Set username/password
        else LDAPEnvPassword missing
            App-->>App: ❌ Error & abort
        end
    else LDAPUsername & LDAPPassword provided
        App->>LDAP: Set username/password (legacy)
    end
    
    App->>LDAP: Proceed with connection
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • src/Sharphound.cs: Review credential precedence logic, error handling paths, and environment variable validation to ensure correct fallback behavior and no unintended early returns.
  • src/Options.cs and src/JsonExtensions.cs: Straightforward property/class additions; verify naming and property types match usage in Sharphound.cs.

Poem

🐰 Three paths now lead to LDAP's door,
JSON, env, or username before,
Credentials flex in newer ways,
Sharp precision through the maze!
Hop hop 🗝️

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature added: support for loading LDAP credentials from environment variables or JSON files.
Description check ✅ Passed The pull request description covers all key template sections: a clear description of changes, motivation/context explaining the use case, testing methodology across multiple AD domains, and proper type classification. While test coverage checklist is unchecked (with honest explanation), the overall description is substantially complete.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Sharphound.cs (1)

103-131: Document or enforce mutual exclusivity of credential sources.

Multiple credential sources can be specified simultaneously (JSON file, environment variables, and legacy username/password), creating undocumented precedence behavior. Currently, if all three are specified, each overwrites the previous one in sequence, with legacy credentials taking final precedence. This could lead to confusion and unexpected behavior.

Consider adding validation to ensure only one credential source is used:

                     if (options.DomainController != null) ldapOptions.Server = options.DomainController;

+                    // Validate that only one credential source is specified
+                    int credentialSourceCount = 0;
+                    if (options.LDAPCredentialJsonPath != null) credentialSourceCount++;
+                    if (options.LDAPEnvUsername != null) credentialSourceCount++;
+                    if (options.LDAPUsername != null) credentialSourceCount++;
+                    
+                    if (credentialSourceCount > 1) {
+                        logger.LogError("Multiple credential sources specified. Please use only one of: --ldapcredentialsjsonpath, --ldapenvusername, or --ldapusername");
+                        return;
+                    }
+
                     if (options.LDAPCredentialJsonPath != null) {
🧹 Nitpick comments (1)
src/Sharphound.cs (1)

116-120: Follow C# naming conventions for local variables.

Local variables EnvUsername and EnvPassword should use camelCase rather than PascalCase per C# conventions.

Apply this diff:

-                        string EnvUsername = Environment.GetEnvironmentVariable(options.LDAPEnvUsername);
-                        string EnvPassword = Environment.GetEnvironmentVariable(options.LDAPEnvPassword);
+                        string envUsername = Environment.GetEnvironmentVariable(options.LDAPEnvUsername);
+                        string envPassword = Environment.GetEnvironmentVariable(options.LDAPEnvPassword);

-                        ldapOptions.Username = EnvUsername;
-                        ldapOptions.Password = EnvPassword;
+                        ldapOptions.Username = envUsername;
+                        ldapOptions.Password = envPassword;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc420a6 and eb52074.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • src/JsonExtensions.cs (1 hunks)
  • src/Options.cs (1 hunks)
  • src/Sharphound.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/Sharphound.cs (1)
src/JsonExtensions.cs (1)
  • Credentials (12-16)
🔇 Additional comments (2)
src/JsonExtensions.cs (1)

12-16: LGTM with validation recommendation.

The Credentials class design is appropriate for JSON deserialization. However, ensure that validation of the deserialized values (non-null, non-empty) occurs at the usage site.

README.md (1)

80-84: Fix typo: "enviroment" → "environment".

The word "environment" is misspelled in the documentation for the new LDAP credential options.

Apply this diff:

-  --ldapenvusername          Username for LDAP stored in a defined environment variable (argument takes the environment variable as input)
+  --ldapenvusername          Username for LDAP stored in a defined environment variable (argument takes the environment variable name as input)

-  --ldapenvpassword          Password for LDAP stored in a defined environment variable (argument takes the environment variable as input)
+  --ldapenvpassword          Password for LDAP stored in a defined environment variable (argument takes the environment variable name as input)

Likely an incorrect or invalid review comment.

Comment on lines +84 to +88
[Option(HelpText = "Enviroment variable name for LDAP Username", Default = null)]
public string LDAPEnvUsername { get; set; }

[Option(HelpText = "Enviroment variable name for LDAP Password", Default = null)]
public string LDAPEnvPassword { get; set; }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo: "Enviroment" → "Environment".

The help text contains a spelling error that should be corrected for consistency and professionalism.

Apply this diff:

-        [Option(HelpText = "Enviroment variable name for LDAP Username", Default = null)]
+        [Option(HelpText = "Environment variable name for LDAP Username", Default = null)]
         public string LDAPEnvUsername { get; set; }

-        [Option(HelpText = "Enviroment variable name for LDAP Password", Default = null)]
+        [Option(HelpText = "Environment variable name for LDAP Password", Default = null)]
         public string LDAPEnvPassword { get; set; }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[Option(HelpText = "Enviroment variable name for LDAP Username", Default = null)]
public string LDAPEnvUsername { get; set; }
[Option(HelpText = "Enviroment variable name for LDAP Password", Default = null)]
public string LDAPEnvPassword { get; set; }
[Option(HelpText = "Environment variable name for LDAP Username", Default = null)]
public string LDAPEnvUsername { get; set; }
[Option(HelpText = "Environment variable name for LDAP Password", Default = null)]
public string LDAPEnvPassword { get; set; }
🤖 Prompt for AI Agents
In src/Options.cs around lines 84 to 88, the HelpText strings for
LDAPEnvUsername and LDAPEnvPassword contain a typo "Enviroment"; update both
HelpText values to read "Environment variable name for LDAP Username" and
"Environment variable name for LDAP Password" respectively to correct the
spelling.

Comment on lines +103 to +108
if (options.LDAPCredentialJsonPath != null) {
string json = File.ReadAllText(options.LDAPCredentialJsonPath);
Credentials ldapCreds = JsonConvert.DeserializeObject<Credentials>(json);
ldapOptions.Username = ldapCreds.Username;
ldapOptions.Password = ldapCreds.Password;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add missing using statements and error handling for JSON credential loading.

The code references File.ReadAllText and JsonConvert.DeserializeObject but the required using statements (System.IO and Newtonsoft.Json) are not present in the file. Additionally, there is no error handling for file operations or JSON deserialization failures.

Apply this diff to add the required using statements at the top of the file:

 using System;
 using System.DirectoryServices.Protocols;
+using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
 using CommandLine;
 using Microsoft.Extensions.Logging;
 using Microsoft.Win32;
+using Newtonsoft.Json;
 using Sharphound.Client;
 using SharpHoundCommonLib;
 using SharpHoundCommonLib.Enums;

Then apply this diff to add error handling and validation:

                     if (options.LDAPCredentialJsonPath != null) {
-                        string json = File.ReadAllText(options.LDAPCredentialJsonPath);
-                        Credentials ldapCreds = JsonConvert.DeserializeObject<Credentials>(json);
-                        ldapOptions.Username = ldapCreds.Username;
-                        ldapOptions.Password = ldapCreds.Password;
+                        try {
+                            string json = File.ReadAllText(options.LDAPCredentialJsonPath);
+                            Credentials ldapCreds = JsonConvert.DeserializeObject<Credentials>(json);
+                            
+                            if (ldapCreds == null || string.IsNullOrEmpty(ldapCreds.Username) || string.IsNullOrEmpty(ldapCreds.Password)) {
+                                logger.LogError("Invalid credentials in JSON file. Both Username and Password must be provided.");
+                                return;
+                            }
+                            
+                            ldapOptions.Username = ldapCreds.Username;
+                            ldapOptions.Password = ldapCreds.Password;
+                        } catch (FileNotFoundException) {
+                            logger.LogError("LDAP credentials JSON file not found at path: {Path}", options.LDAPCredentialJsonPath);
+                            return;
+                        } catch (JsonException ex) {
+                            logger.LogError("Failed to parse LDAP credentials JSON file: {Message}", ex.Message);
+                            return;
+                        } catch (Exception ex) {
+                            logger.LogError("Error reading LDAP credentials from JSON file: {Message}", ex.Message);
+                            return;
+                        }
                     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/Sharphound.cs around lines 103 to 108, the code uses File.ReadAllText and
JsonConvert.DeserializeObject but lacks the required using directives and any
error handling; add using System.IO; and using Newtonsoft.Json; at the top of
the file, then wrap the file-read and deserialization block in a try-catch that
catches IO and JSON exceptions (e.g., IOException, JsonException, and a general
Exception fallback), validate that the deserialized Credentials object and its
Username/Password are not null or empty, and on error either log a clear message
and rethrow or throw a new descriptive exception so callers can handle the
failure instead of letting unhandled exceptions propagate silently.

Comment on lines +110 to +121
if (options.LDAPEnvUsername != null) {
if (options.LDAPEnvPassword == null) {
logger.LogError("You must specify LDAPEnvPassword if using the LDAPEnvUsername options");
return;
}

string EnvUsername = Environment.GetEnvironmentVariable(options.LDAPEnvUsername);
string EnvPassword = Environment.GetEnvironmentVariable(options.LDAPEnvPassword);

ldapOptions.Username = EnvUsername;
ldapOptions.Password = EnvPassword;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation for environment variable credential values.

The code retrieves credentials from environment variables but doesn't validate that the variables exist or contain non-empty values. If an environment variable doesn't exist, Environment.GetEnvironmentVariable returns null, which would set invalid credentials silently.

Apply this diff:

                     if (options.LDAPEnvUsername != null) {
                         if (options.LDAPEnvPassword == null) {
                             logger.LogError("You must specify LDAPEnvPassword if using the LDAPEnvUsername options");
                             return;
                         }

                         string EnvUsername = Environment.GetEnvironmentVariable(options.LDAPEnvUsername);
                         string EnvPassword = Environment.GetEnvironmentVariable(options.LDAPEnvPassword);
+                        
+                        if (string.IsNullOrEmpty(EnvUsername)) {
+                            logger.LogError("Environment variable {VarName} not found or empty", options.LDAPEnvUsername);
+                            return;
+                        }
+                        
+                        if (string.IsNullOrEmpty(EnvPassword)) {
+                            logger.LogError("Environment variable {VarName} not found or empty", options.LDAPEnvPassword);
+                            return;
+                        }

                         ldapOptions.Username = EnvUsername;
                         ldapOptions.Password = EnvPassword;
                     }
🤖 Prompt for AI Agents
In src/Sharphound.cs around lines 110 to 121, the code reads LDAP credentials
from environment variables but does not validate that the retrieved values are
present or non-empty; update the block to check
Environment.GetEnvironmentVariable(...) results with string.IsNullOrWhiteSpace
for both username and password, and if either is null/empty log a clear error
referencing the offending options (options.LDAPEnvUsername or
options.LDAPEnvPassword) and return early instead of assigning invalid
credentials to ldapOptions.Username/Password.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants