
A practical client for ADWS in Golang.
A practical client for ADWS in Golang.
Sopa implements the ADWS protocol stack (MS-NNS + MC-NMF + SOAP), exposing the following command-line features:
query: runs LDAP-filter searches via WS-Enumeration Enumerate + Pull loop with attribute projection, scope control (Base/OneLevel/Subtree), and paginationget: fetches a single object by DN via WS-Transfer Getcreate: creates objects via WS-Transfer ResourceFactory (built-in types: user, computer, group, OU, container; or custom objects from a YAML template via IMDA AddRequest)delete: removes an object by DN via WS-Transfer Deleteattr: adds, replaces, or removes individual attribute values on an existing object via WS-Transfer Putset-password: sets an account password via MS-ADCAP SetPasswordchange-password: changes an account password (requires the old password) via MS-ADCAP ChangePasswordtranslate-name: converts between DN and canonical name formats via TranslateNamegroups: lists group memberships or authorization groups of a principal via GetADPrincipalGroupMembership / GetADPrincipalAuthorizationGroupmembers: enumerates group members (optionally recursive) via GetADGroupMemberoptfeature: toggles optional AD features (e.g. Recycle Bin) via ChangeOptionalFeatureinfo: retrieves topology metadata (version, domain, forest, DC list) via GetVersion, GetADDomain, GetADForest, GetADDomainControllersmex: fetches ADWS service endpoint metadata via an unauthenticated WS-MetadataExchange request$ go install github.com/Macmod/sopa/cmd/sopa@latest
# Auth flags (-u, -p, -d, -k, -H, -c, ...) are omitted for brevity - see Authentication section.
# Search objects by LDAP filter
$ sopa [auth_flags] query --dc <DC> --filter '(objectClass=*)'
# Fetch a single object by DN
$ sopa [auth_flags] get --dc <DC> --dn '<DN>'
# Delete an object by DN
$ sopa [auth_flags] delete --dc <DC> --dn '<DN>'
# Edit attribute values
$ sopa [auth_flags] attr add --dc <DC> --dn '<DN>' --attr <ATTR> --value <VALUE>
$ sopa [auth_flags] attr replace --dc <DC> --dn '<DN>' --attr <ATTR> --value <VALUE>
$ sopa [auth_flags] attr delete --dc <DC> --dn '<DN>' --attr <ATTR>
# Create objects
$ sopa [auth_flags] create user --dc <DC> --name <CN> --pass <INITIAL_PASS>
$ sopa [auth_flags] create computer --dc <DC> --name <CN>
$ sopa [auth_flags] create group --dc <DC> --name <CN> --type GlobalSecurity
$ sopa [auth_flags] create ou --dc <DC> --name <CN>
$ sopa [auth_flags] create container --dc <DC> --name <CN>
$ sopa [auth_flags] create custom --dc <DC> --template <TEMPLATE.yaml>
# Set / change account passwords (MS-ADCAP)
$ sopa [auth_flags] set-password --dc <DC> --dn '<DN>' --new <NEW_PASS>
$ sopa [auth_flags] change-password --dc <DC> --dn '<DN>' --old <OLD_PASS> --new <NEW_PASS>
# Translate DN <-> canonical name (MS-ADCAP)
# (this call is mostly useless but kept for completeness 😄)
$ sopa [auth_flags] translate-name --dc <DC> --offered DistinguishedName --desired CanonicalName '<DN>'
# Principal group memberships (MS-ADCAP)
$ sopa [auth_flags] groups --dc <DC> --dn '<DN>' --membership --authz
# Group members (MS-ADCAP)
$ sopa [auth_flags] members --dc <DC> --dn '<GROUP_DN>' --recursive
# Toggle optional AD feature, e.g. Recycle Bin (MS-ADCAP)
$ sopa [auth_flags] optfeature --dc <DC> --feature-id <FEATURE_GUID> --enable
# Topology info (MS-ADCAP)
$ sopa [auth_flags] info version --dc <DC>
$ sopa [auth_flags] info domain --dc <DC>
$ sopa [auth_flags] info forest --dc <DC>
$ sopa [auth_flags] info dcs --dc <DC>
# ADWS service endpoint metadata (unauthenticated - auth flags not needed)
$ sopa mex --dc <DC>
Run sopa without a subcommand to open an interactive shell. It reuses a single connection for all commands and provides tab-completion.
$ sopa --dc <DC> -u <USER> -p <PASS> -d <DOMAIN>
sopa v1.1.0
Connected dc.corp.local domain=corp.local user=Administrator
Type 'help' for commands or 'exit' to quit.
[corp.local]> query --filter '(objectClass=user)' --attrs sAMAccountName
[corp.local]> get --dn 'CN=Administrator,CN=Users,DC=corp,DC=local'
[corp.local]> exit
Use exit, quit, or Ctrl-D to leave the shell.
Example template: examples/custom-create.example.yaml
Template schema (YAML):
parentDN (string, required): container DNrdn (string, required): relative DN for the new object (e.g. CN=Foo)attributes (list, required): each item has:
name (string, required): attribute name (cn or addata:cn)type (string, optional): string|int|bool|base64|hex (or explicit xsd:*), default stringvalue (string) or values (list of strings)Notes:
ad:relativeDistinguishedName or ad:container-hierarchy-parent in the template (they are injected automatically).hex values are converted to xsd:base64Binary.value: "".--dc accepts a FQDN, an IP address, or can be omitted.
Because the DC's hostname is sometimes not available from the network's default DNS, it is strongly recommended to always pass --dns <DC-IP> so that sopa uses the DC's own DNS server for all lookups:
# Option 1: let sopa resolve everything through the DC's DNS
$ sopa query --dns 192.168.1.10 -d corp.local -u user -p pass --filter '(objectClass=user)'
When --dc is omitted and --domain is provided, sopa discovers a DC
automatically by querying SRV records:
_ldap._tcp.<domain> (tried first)
_kerberos._tcp.<domain> (fallback)
The target of the highest-priority record is used. This requires that the
DNS server pointed to by --dns can answer those SRV queries - the DC's own
integrated DNS server (when present) should be capable of that.
# Option 2: provide DC explicitly without Kerberos
$ sopa info version --dc 192.168.1.10 --domain corp.local -u user -p pass
When an IP is provided for --dc, the IP is always resolved to an FQDN via a reverse PTR lookup - the ADWS endpoint's WCF address filter requires an FQDN in the wsa:To header regardless of authentication method.
This PTR lookup also goes through --dns, so a correctly configured reverse
zone on the DC is required: