Add a host analyzer to check if a subnet contains an IP address (#1735)

* Add a host collector / analyzer to check if a subnet contains an IP address
This commit is contained in:
Salah Al Saleh
2025-02-13 13:16:59 -08:00
committed by GitHub
parent 716dda221d
commit d5a6b19417
13 changed files with 626 additions and 0 deletions

View File

@@ -2746,6 +2746,61 @@ spec:
required:
- outcomes
type: object
subnetContainsIP:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
cidr:
type: string
collectorName:
type: string
exclude:
type: BoolString
ip:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- cidr
- ip
- outcomes
type: object
sysctl:
properties:
annotations:

View File

@@ -895,6 +895,61 @@ spec:
required:
- outcomes
type: object
subnetContainsIP:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
cidr:
type: string
collectorName:
type: string
exclude:
type: BoolString
ip:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- cidr
- ip
- outcomes
type: object
sysctl:
properties:
annotations:

View File

@@ -895,6 +895,61 @@ spec:
required:
- outcomes
type: object
subnetContainsIP:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
cidr:
type: string
collectorName:
type: string
exclude:
type: BoolString
ip:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- cidr
- ip
- outcomes
type: object
sysctl:
properties:
annotations:

View File

@@ -19837,6 +19837,61 @@ spec:
required:
- outcomes
type: object
subnetContainsIP:
properties:
annotations:
additionalProperties:
type: string
type: object
checkName:
type: string
cidr:
type: string
collectorName:
type: string
exclude:
type: BoolString
ip:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
strict:
type: BoolString
required:
- cidr
- ip
- outcomes
type: object
sysctl:
properties:
annotations:

View File

@@ -0,0 +1,16 @@
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: subnet-contains-ip
spec:
analyzers:
- subnetContainsIP:
cidr: "10.0.0.0/8"
ip: "10.0.0.5"
outcomes:
- fail:
when: "false"
message: The IP address is not within the subnet range
- pass:
when: "true"
message: The IP address is within the subnet range

View File

@@ -45,6 +45,8 @@ func GetHostAnalyzer(analyzer *troubleshootv1beta2.HostAnalyze) (HostAnalyzer, b
return &AnalyzeHostIPV4Interfaces{analyzer.IPV4Interfaces}, true
case analyzer.SubnetAvailable != nil:
return &AnalyzeHostSubnetAvailable{analyzer.SubnetAvailable}, true
case analyzer.SubnetContainsIP != nil:
return &AnalyzeHostSubnetContainsIP{analyzer.SubnetContainsIP}, true
case analyzer.FilesystemPerformance != nil:
return &AnalyzeHostFilesystemPerformance{analyzer.FilesystemPerformance}, true
case analyzer.Certificate != nil:

View File

@@ -0,0 +1,55 @@
package analyzer
import (
"fmt"
"net"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)
type AnalyzeHostSubnetContainsIP struct {
hostAnalyzer *troubleshootv1beta2.SubnetContainsIPAnalyze
}
func (a *AnalyzeHostSubnetContainsIP) Title() string {
return hostAnalyzerTitleOrDefault(a.hostAnalyzer.AnalyzeMeta, "Subnet Contains IP")
}
func (a *AnalyzeHostSubnetContainsIP) IsExcluded() (bool, error) {
return isExcluded(a.hostAnalyzer.Exclude)
}
func (a *AnalyzeHostSubnetContainsIP) Analyze(
getCollectedFileContents func(string) ([]byte, error), findFiles getChildCollectedFileContents,
) ([]*AnalyzeResult, error) {
_, ipNet, err := net.ParseCIDR(a.hostAnalyzer.CIDR)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse CIDR %s", a.hostAnalyzer.CIDR)
}
ip := net.ParseIP(a.hostAnalyzer.IP)
if ip == nil {
return nil, errors.Errorf("failed to parse IP address %s", a.hostAnalyzer.IP)
}
contains := fmt.Sprintf("%t", ipNet.Contains(ip))
results, err := analyzeHostCollectorResults([]collectedContent{{Data: []byte(contains)}}, a.hostAnalyzer.Outcomes, a.CheckCondition, a.Title())
if err != nil {
return nil, errors.Wrap(err, "failed to analyze Subnet Contains IP")
}
return results, nil
}
func (a *AnalyzeHostSubnetContainsIP) CheckCondition(when string, data []byte) (bool, error) {
switch when {
case "true":
return string(data) == "true", nil
case "false":
return string(data) == "false", nil
}
return false, errors.Errorf("unknown condition: %q", when)
}

View File

@@ -0,0 +1,122 @@
package analyzer
import (
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnalyzeSubnetContainsIP(t *testing.T) {
tests := []struct {
name string
hostAnalyzer *troubleshootv1beta2.SubnetContainsIPAnalyze
result []*AnalyzeResult
expectErr bool
}{
{
name: "ip is in subnet",
hostAnalyzer: &troubleshootv1beta2.SubnetContainsIPAnalyze{
CIDR: "10.0.0.0/8",
IP: "10.0.0.5",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "true",
Message: "IP address is in subnet",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "false",
Message: "IP address is not in subnet",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "Subnet Contains IP",
IsPass: true,
Message: "IP address is in subnet",
},
},
},
{
name: "ip is not in subnet",
hostAnalyzer: &troubleshootv1beta2.SubnetContainsIPAnalyze{
CIDR: "10.0.0.0/8",
IP: "192.168.1.1",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "true",
Message: "IP address is in subnet",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "false",
Message: "IP address is not in subnet",
},
},
},
},
result: []*AnalyzeResult{
{
Title: "Subnet Contains IP",
IsFail: true,
Message: "IP address is not in subnet",
},
},
},
{
name: "invalid CIDR",
hostAnalyzer: &troubleshootv1beta2.SubnetContainsIPAnalyze{
CIDR: "invalid",
IP: "10.0.0.5",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "true",
Message: "IP address is in subnet",
},
},
},
},
expectErr: true,
},
{
name: "invalid IP",
hostAnalyzer: &troubleshootv1beta2.SubnetContainsIPAnalyze{
CIDR: "10.0.0.0/8",
IP: "invalid",
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "true",
Message: "IP address is in subnet",
},
},
},
},
expectErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
result, err := (&AnalyzeHostSubnetContainsIP{test.hostAnalyzer}).Analyze(nil, nil)
if test.expectErr {
req.Error(err)
return
}
req.NoError(err)
assert.Equal(t, test.result, result)
})
}
}

View File

@@ -98,6 +98,14 @@ type SubnetAvailableAnalyze struct {
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type SubnetContainsIPAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
CIDR string `json:"cidr" yaml:"cidr"`
IP string `json:"ip" yaml:"ip"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type FilesystemPerformanceAnalyze struct {
AnalyzeMeta `json:",inline" yaml:",inline"`
CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"`
@@ -157,6 +165,7 @@ type HostAnalyze struct {
TCPConnect *TCPConnectAnalyze `json:"tcpConnect,omitempty" yaml:"tcpConnect,omitempty"`
IPV4Interfaces *IPV4InterfacesAnalyze `json:"ipv4Interfaces,omitempty" yaml:"ipv4Interfaces,omitempty"`
SubnetAvailable *SubnetAvailableAnalyze `json:"subnetAvailable,omitempty" yaml:"subnetAvailable,omitempty"`
SubnetContainsIP *SubnetContainsIPAnalyze `json:"subnetContainsIP,omitempty" yaml:"subnetContainsIP,omitempty"`
FilesystemPerformance *FilesystemPerformanceAnalyze `json:"filesystemPerformance,omitempty" yaml:"filesystemPerformance,omitempty"`
Certificate *CertificateAnalyze `json:"certificate,omitempty" yaml:"certificate,omitempty"`
CertificatesCollection *HostCertificatesCollectionAnalyze `json:"certificatesCollection,omitempty" yaml:"certificatesCollection,omitempty"`

View File

@@ -1918,6 +1918,11 @@ func (in *HostAnalyze) DeepCopyInto(out *HostAnalyze) {
*out = new(SubnetAvailableAnalyze)
(*in).DeepCopyInto(*out)
}
if in.SubnetContainsIP != nil {
in, out := &in.SubnetContainsIP, &out.SubnetContainsIP
*out = new(SubnetContainsIPAnalyze)
(*in).DeepCopyInto(*out)
}
if in.FilesystemPerformance != nil {
in, out := &in.FilesystemPerformance, &out.FilesystemPerformance
*out = new(FilesystemPerformanceAnalyze)
@@ -4814,6 +4819,33 @@ func (in *SubnetAvailableAnalyze) DeepCopy() *SubnetAvailableAnalyze {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SubnetContainsIPAnalyze) DeepCopyInto(out *SubnetContainsIPAnalyze) {
*out = *in
in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta)
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetContainsIPAnalyze.
func (in *SubnetContainsIPAnalyze) DeepCopy() *SubnetContainsIPAnalyze {
if in == nil {
return nil
}
out := new(SubnetContainsIPAnalyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SupportBundle) DeepCopyInto(out *SupportBundle) {
*out = *in

View File

@@ -279,6 +279,8 @@ func (c *RemoteCollector) toHostCollector() (*troubleshootv1beta2.HostCollect, e
CollectorName: c.Collect.IPV4Interfaces.CollectorName,
Exclude: c.Collect.IPV4Interfaces.Exclude,
},
CIDRRangeAlloc: c.Collect.SubnetAvailable.CIDRRangeAlloc,
DesiredCIDR: c.Collect.SubnetAvailable.DesiredCIDR,
}
case c.Collect.FilesystemPerformance != nil:
hostCollect.FilesystemPerformance = &troubleshootv1beta2.FilesystemPerformance{

View File

@@ -4174,6 +4174,90 @@
}
}
},
"subnetContainsIP": {
"type": "object",
"required": [
"cidr",
"ip",
"outcomes"
],
"properties": {
"annotations": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"checkName": {
"type": "string"
},
"cidr": {
"type": "string"
},
"collectorName": {
"type": "string"
},
"exclude": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
},
"ip": {
"type": "string"
},
"outcomes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fail": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"pass": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"warn": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
}
}
}
},
"strict": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
}
}
},
"sysctl": {
"type": "object",
"required": [

View File

@@ -18765,6 +18765,90 @@
}
}
},
"subnetContainsIP": {
"type": "object",
"required": [
"cidr",
"ip",
"outcomes"
],
"properties": {
"annotations": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"checkName": {
"type": "string"
},
"cidr": {
"type": "string"
},
"collectorName": {
"type": "string"
},
"exclude": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
},
"ip": {
"type": "string"
},
"outcomes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fail": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"pass": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
},
"warn": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"uri": {
"type": "string"
},
"when": {
"type": "string"
}
}
}
}
}
},
"strict": {
"oneOf": [{"type": "string"},{"type": "boolean"}]
}
}
},
"sysctl": {
"type": "object",
"required": [