Relation Interface¶
This document explains the Relation interface and the relationship taxonomy system in the open-asset-model. The Relation interface defines how assets connect to each other through labeled, typed relationships. This page covers:
- The three required methods of the
Relationinterface - The
RelationTypeenumeration (5 distinct types) - The relationship taxonomy system that defines valid connections between asset types
- Query and validation functions for relationship integrity
For information about the assets being connected, see Asset Interface. For information about non-relational metadata attached to assets, see Property Interface. For detailed documentation on specific relationship implementations, see DNS Relationship Types and General Relationship Types.
The Relation Interface¶
The Relation interface defines the contract that all relationship implementations must satisfy. It consists of three methods that enable uniform handling of connections between assets.
Interface Definition¶
Method Specifications¶
| Method | Return Type | Purpose |
|---|---|---|
Label() |
string |
Returns the semantic relationship label (e.g., "dns_record", "port", "parent") |
RelationType() |
RelationType |
Returns the type constant identifying the relationship implementation |
JSON() |
([]byte, error) |
Serializes the relationship data to JSON format for persistence/transport |
The Label() method returns a semantic identifier that describes the nature of the connection. Examples include "dns_record" for DNS resolution relationships, "port" for service port connections, or "parent" for organizational hierarchies. These labels are defined in the relationship taxonomy maps and must be lowercase when validated .
The RelationType() method returns one of five enumerated constants that classify the relationship's implementation type. This enables type-based dispatching and validation logic.
The JSON() method enables serialization of relationship-specific data. For DNS relationships, this includes record-specific fields like preference values for MX records or priority/weight for SRV records.
RelationType Enumeration¶
The RelationType is a string-based enumeration that categorizes relationship implementations. The model defines exactly five relationship types, each serving distinct semantic purposes.
Defined Relationship Types¶
graph LR
RelationType["RelationType<br/>(string type alias)"]
RelationType --> BasicDNS["BasicDNSRelation<br/>A, AAAA, CNAME, NS records"]
RelationType --> Port["PortRelation<br/>Service port connections"]
RelationType --> Pref["PrefDNSRelation<br/>MX records with preference"]
RelationType --> Simple["SimpleRelation<br/>Generic connections"]
RelationType --> SRV["SRVDNSRelation<br/>Service records with priority/weight"]
| Constant | String Value | Usage |
|---|---|---|
BasicDNSRelation |
"BasicDNSRelation" | Standard DNS record types: A, AAAA, CNAME, NS |
PortRelation |
"PortRelation" | Network service port connections from FQDN/IPAddress to Service |
PrefDNSRelation |
"PrefDNSRelation" | DNS MX records that include a preference value |
SimpleRelation |
"SimpleRelation" | General-purpose connections without specialized semantics |
SRVDNSRelation |
"SRVDNSRelation" | DNS SRV records with priority, weight, and port data |
The RelationList variable at provides a slice containing all five types for iteration purposes.
The predominance of DNS-specific relationship types (3 out of 5) reflects the model's origins in network reconnaissance and attack surface discovery. The distinction between BasicDNSRelation, PrefDNSRelation, and SRVDNSRelation allows implementations to properly model the different data structures of DNS record types while maintaining type safety.
Relationship Taxonomy System¶
The relationship taxonomy defines which asset types can connect to which other asset types, using specific labels and RelationTypes. This three-level nested map structure enforces graph integrity at the model level.
Architecture Overview¶
graph TB
API["Public Query Functions"]
GetOutgoing["GetAssetOutgoingRelations(subject)<br/>Returns: []string labels"]
GetTransform["GetTransformAssetTypes(subject, label, rtype)<br/>Returns: []AssetType destinations"]
ValidRel["ValidRelationship(src, label, rtype, dest)<br/>Returns: bool"]
Dispatcher["assetTypeRelations(atype)<br/>Central Dispatcher Function<br/>Switch statement on AssetType"]
Maps["Asset-Specific Relationship Maps"]
fqdnRels["fqdnRels<br/>map[string]map[RelationType][]AssetType"]
ipRels["ipRels<br/>map[string]map[RelationType][]AssetType"]
orgRels["orgRels<br/>map[string]map[RelationType][]AssetType"]
serviceRels["serviceRels<br/>map[string]map[RelationType][]AssetType"]
API --> GetOutgoing
API --> GetTransform
API --> ValidRel
GetOutgoing --> Dispatcher
GetTransform --> Dispatcher
ValidRel --> GetTransform
Dispatcher --> fqdnRels
Dispatcher --> ipRels
Dispatcher --> orgRels
Dispatcher --> serviceRels
The Three-Level Map Structure¶
Each asset type's relationships are defined as:
This nesting provides:
- Level 1 (Label): String key representing the semantic relationship (e.g., "dns_record", "port")
- Level 2 (RelationType): Which RelationType implementations can use this label
- Level 3 (AssetType slice): Valid destination asset types for this label+RelationType combination
Example: FQDN Relationships¶
The fqdnRels map at demonstrates the taxonomy structure:
var fqdnRels = map[string]map[RelationType][]AssetType{
"port": {PortRelation: {Service}},
"dns_record": {
BasicDNSRelation: {FQDN, IPAddress},
PrefDNSRelation: {FQDN},
SRVDNSRelation: {FQDN},
},
"node": {SimpleRelation: {FQDN}},
"registration": {SimpleRelation: {DomainRecord}},
}
This specification declares:
- An FQDN can have a "port" relationship (via PortRelation) to a Service asset
- An FQDN can have a "dns_record" relationship to either FQDN or IPAddress when using BasicDNSRelation, but only to FQDN when using PrefDNSRelation or SRVDNSRelation
- An FQDN can have a "node" relationship (subdomain hierarchy) to another FQDN
- An FQDN can have a "registration" relationship to a DomainRecord (WHOIS data)
Complete Taxonomy Coverage¶
The model defines relationship maps for 19 of the 21 asset types. The two asset types without defined outgoing relationships are those that serve as leaf nodes in typical graph structures.
| Asset Type | Variable Name | Line Reference |
|---|---|---|
| Account | accountRels |
|
| AutnumRecord | autnumRecordRels |
|
| AutonomousSystem | autonomousSystemRels |
|
| ContactRecord | contactRecordRels |
|
| DomainRecord | domainRecordRels |
|
| File | fileRels |
|
| FQDN | fqdnRels |
|
| FundsTransfer | fundsTransferRels |
|
| Identifier | identifierRels |
|
| IPAddress | ipRels |
|
| IPNetRecord | ipnetRecordRels |
|
| Location | locationRels |
|
| Netblock | netblockRels |
|
| Organization | orgRels |
|
| Person | personRels |
|
| Phone | phoneRels |
|
| Product | productRels |
|
| ProductRelease | productReleaseRels |
|
| Service | serviceRels |
|
| TLSCertificate | tlscertRels |
|
| URL | urlRels |
Central Dispatcher: assetTypeRelations Function¶
The assetTypeRelations function serves as the central dispatcher that maps an AssetType to its relationship taxonomy map. All query and validation functions route through this function.
Implementation Pattern¶
graph TD
Input["assetTypeRelations(atype AssetType)"]
Switch["Switch statement on atype"]
Account["case Account:<br/>return accountRels"]
FQDN["case FQDN:<br/>return fqdnRels"]
IPAddress["case IPAddress:<br/>return ipRels"]
Org["case Organization:<br/>return orgRels"]
Service["case Service:<br/>return serviceRels"]
TLS["case TLSCertificate:<br/>return tlscertRels"]
Default["default:<br/>return nil"]
Output["map[string]map[RelationType][]AssetType"]
Input --> Switch
Switch --> Account
Switch --> FQDN
Switch --> IPAddress
Switch --> Org
Switch --> Service
Switch --> TLS
Switch --> Default
Account --> Output
FQDN --> Output
IPAddress --> Output
Org --> Output
Service --> Output
TLS --> Output
Default --> Output
The function uses a switch statement at that exhaustively matches all defined asset types. Asset types without outgoing relationships (those not present in any relationship map) cause the default case to return nil.
Validation and Query Functions¶
The model provides three public API functions for querying and validating relationships. These functions abstract the complexity of the nested map structure.
Function Signatures and Purposes¶
| Function | Signature | Purpose |
|---|---|---|
GetAssetOutgoingRelations |
(subject AssetType) []string |
Returns all relationship labels valid for outgoing connections from the subject asset type |
GetTransformAssetTypes |
(subject AssetType, label string, rtype RelationType) []AssetType |
Returns the asset types that can be destinations for a specific label+RelationType combination |
ValidRelationship |
(src AssetType, label string, rtype RelationType, destination AssetType) bool |
Validates whether a proposed relationship conforms to the taxonomy |
GetAssetOutgoingRelations¶
This function returns the set of valid relationship labels for a given asset type. It queries assetTypeRelations and extracts the keys from the first-level map.
sequenceDiagram
participant Client
participant GetOutgoing as "GetAssetOutgoingRelations"
participant Dispatcher as "assetTypeRelations"
Client->>GetOutgoing: GetAssetOutgoingRelations(FQDN)
GetOutgoing->>Dispatcher: assetTypeRelations(FQDN)
Dispatcher-->>GetOutgoing: fqdnRels map
GetOutgoing->>GetOutgoing: Extract keys from map
GetOutgoing-->>Client: ["port", "dns_record", "node", "registration"]
Implementation at . Returns nil if the asset type has no defined relationships.
GetTransformAssetTypes¶
This function returns the valid destination asset types for a specific (subject, label, RelationType) tuple. The label is normalized to lowercase at before lookup.
The function deduplicates results using a map to prevent duplicate entries when the same destination type appears multiple times in the taxonomy .
Example usage:
// What asset types can FQDN.dns_record.BasicDNSRelation point to?
destinations := GetTransformAssetTypes(FQDN, "dns_record", BasicDNSRelation)
// Returns: [FQDN, IPAddress]
// What about PrefDNSRelation?
destinations = GetTransformAssetTypes(FQDN, "dns_record", PrefDNSRelation)
// Returns: [FQDN]
Implementation at .
ValidRelationship¶
This function validates a complete relationship specification: source type, label, relation type, and destination type. It uses GetTransformAssetTypes internally and checks whether the destination appears in the allowed types .
graph LR
Input["ValidRelationship(src, label, rtype, dest)"]
Query["Call GetTransformAssetTypes(src, label, rtype)"]
Check["Iterate returned AssetTypes"]
Match{"dest matches<br/>any returned type?"}
ReturnTrue["return true"]
ReturnFalse["return false"]
Input --> Query
Query --> Check
Check --> Match
Match -->|Yes| ReturnTrue
Match -->|No| ReturnFalse
This function is critical for data integrity enforcement. Discovery tools should call ValidRelationship before storing or emitting relationships to ensure graph consistency.
Relationship Label Semantics¶
While the taxonomy defines the structural validity of relationships, labels carry semantic meaning that guides their usage. Common patterns include:
Network Infrastructure Labels¶
| Label | Source Types | Semantics |
|---|---|---|
"port" |
FQDN, IPAddress, URL | Indicates a network service running on a specific port |
"dns_record" |
FQDN | DNS resolution relationships (A, AAAA, CNAME, MX, SRV) |
"ptr_record" |
IPAddress | Reverse DNS lookup |
"node" |
FQDN | Subdomain hierarchy (e.g., www.example.com → example.com) |
Registration and Administrative Labels¶
| Label | Source Types | Semantics |
|---|---|---|
"registration" |
FQDN, AutonomousSystem, Netblock | Links to WHOIS/RDAP registration records |
"whois_server" |
DomainRecord, AutnumRecord, IPNetRecord | WHOIS server used for lookups |
"registrar_contact" |
DomainRecord | Contact information for domain registrar |
"admin_contact" |
DomainRecord, AutnumRecord, IPNetRecord | Administrative contact |
"technical_contact" |
DomainRecord, AutnumRecord, IPNetRecord | Technical contact |
Organizational Hierarchy Labels¶
| Label | Source Types | Semantics |
|---|---|---|
"parent" |
Organization | Parent-subsidiary organizational relationship |
"subsidiary" |
Organization | Subsidiary of parent organization |
"sister" |
Organization | Sibling organizations under same parent |
"location" |
Organization, Person | Physical location of entity |
"website" |
Organization, Product, ProductRelease | Official website URL |
Certificate and Cryptography Labels¶
| Label | Source Types | Semantics |
|---|---|---|
"common_name" |
TLSCertificate | Certificate's CN field pointing to FQDN |
"san_dns_name" |
TLSCertificate | Subject Alternative Name DNS entries |
"issuer_contact" |
TLSCertificate | Certificate issuer contact information |
"certificate" |
Service | TLS certificate used by service |
Usage Patterns¶
Discovering Valid Outgoing Relationships¶
Discovery tools can query what relationships are possible from a given asset:
// Example: What relationships can an FQDN have?
labels := GetAssetOutgoingRelations(FQDN)
// Returns: ["port", "dns_record", "node", "registration"]
// For each label, discover valid destination types
for _, label := range labels {
for _, rtype := range RelationList {
destTypes := GetTransformAssetTypes(FQDN, label, rtype)
if len(destTypes) > 0 {
// This label+rtype combination is valid
// destTypes contains valid destination asset types
}
}
}
Validating Before Storage¶
Data ingestion pipelines should validate relationships before persistence:
// Before storing: FQDN --[dns_record/BasicDNSRelation]--> IPAddress
isValid := ValidRelationship(FQDN, "dns_record", BasicDNSRelation, IPAddress)
// Returns: true
// Invalid attempt: FQDN --[dns_record/BasicDNSRelation]--> Service
isValid = ValidRelationship(FQDN, "dns_record", BasicDNSRelation, Service)
// Returns: false
Type-Specific Relationship Discovery¶
The nested map structure allows precise queries:
// What can FQDN's "dns_record" label point to when using BasicDNSRelation?
destinations := GetTransformAssetTypes(FQDN, "dns_record", BasicDNSRelation)
// Returns: [FQDN, IPAddress] - supports A/AAAA records to IPs, CNAME to FQDNs
// What about using PrefDNSRelation (MX records)?
destinations = GetTransformAssetTypes(FQDN, "dns_record", PrefDNSRelation)
// Returns: [FQDN] - MX records only point to mail servers (FQDNs)
Design Rationale¶
Why Three-Level Nesting?¶
The map[string]map[RelationType][]AssetType structure provides fine-grained control over relationship semantics:
- Label level: Groups related concepts (e.g., "dns_record" covers all DNS relationships)
- RelationType level: Distinguishes implementation types with different data structures (BasicDNSRelation vs. PrefDNSRelation have different JSON schemas)
- AssetType slice level: Allows multiple valid destinations (e.g., BasicDNSRelation can point to both FQDN and IPAddress)
Without this nesting, the model would either: - Lose semantic precision (cannot distinguish A records from MX records) - Require label proliferation (separate labels for "a_record", "aaaa_record", "cname_record", etc.)
Why Case-Insensitive Label Matching?¶
The strings.ToLower(label) call at ensures consistent matching regardless of label capitalization in client code. This reduces validation errors from casing inconsistencies while maintaining lowercase in the taxonomy definitions.
Why Return nil Instead of Empty Slices?¶
Functions like GetAssetOutgoingRelations and GetTransformAssetTypes return nil rather than empty slices for invalid inputs , . This distinguishes between:
- Asset types with no relationships defined (returns nil)
- Asset types with relationships defined but none matching the query (returns empty slice)
Callers can use if result == nil to detect taxonomy errors versus legitimate empty result sets.