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

KIALI-2148 Adding mTLS status into status endpoint #803

Merged
merged 9 commits into from Feb 6, 2019
143 changes: 143 additions & 0 deletions business/istio_config.go
Expand Up @@ -65,6 +65,12 @@ var apiToVersion = map[string]string{
kubernetes.ApiAuthenticationVersion: kubernetes.ApiAuthenticationVersion,
}

const (
MeshmTLSEnabled = "MESH_MTLS_ENABLED"
MeshmTLSPartiallyEnabled = "MESH_MTLS_PARTIALLY_ENABLED"
MeshmTLSNotEnabled = "MESH_MTLS_NOT_ENABLED"
)

// GetIstioConfigList returns a list of Istio routing objects, Mixer Rules, (etc.)
// per a given Namespace.
func (in *IstioConfigService) GetIstioConfigList(criteria IstioConfigCriteria) (models.IstioConfigList, error) {
Expand Down Expand Up @@ -476,3 +482,140 @@ func getPermissions(k8s kubernetes.IstioClientInterface, namespace, objectType,
}
return canCreate, (canUpdate || canPatch), canDelete
}

func (in *IstioConfigService) MeshWidemTLSStatus(namespaces []string) (string, error) {
mpp, mpErr := in.hasMeshPolicyEnabled(namespaces)
if mpErr != nil {
return "", mpErr
}

drp, drErr := in.hasDestinationRuleEnabled(namespaces)
if drErr != nil {
return "", drErr
}

if drp && mpp {
return MeshmTLSEnabled, nil
} else if drp || mpp {
return MeshmTLSPartiallyEnabled, nil
}

return MeshmTLSNotEnabled, nil
}

func (in *IstioConfigService) hasMeshPolicyEnabled(namespaces []string) (bool, error) {
if len(namespaces) < 1 {
return false, fmt.Errorf("Can't find MeshPolicies without a namespace")
}

// MeshPolicies are not namespaced. So any namespace user has access to
// will work to retrieve all the MeshPolicies.
mps, err := in.k8s.GetMeshPolicies(namespaces[0])
if err != nil {
return false, err
}

mtlsEnabled := false

for _, mp := range mps {

// It is mandatory to have default as a name
if meshMeta := mp.GetObjectMeta(); meshMeta.Name != "default" {
continue
}

// It is no globally enabled when has targets
targets, targetPresent := mp.GetSpec()["targets"]
specificTarget := targetPresent && len(targets.([]interface{})) > 0
if specificTarget {
continue
}

// It is globally enabled when a peer has mtls enabled
peers, peersPresent := mp.GetSpec()["peers"]
if !peersPresent {
continue
}

for _, peer := range peers.([]interface{}) {
peerMap := peer.(map[string]interface{})
if _, present := peerMap["mtls"]; present {
mtlsEnabled = true
break
}
}
}

return mtlsEnabled, nil
}

func (in *IstioConfigService) hasDestinationRuleEnabled(namespaces []string) (bool, error) {
drs, err := in.getAllDestinationRules(namespaces)
if err != nil {
return false, err
}

mtlsEnabled := false

for _, dr := range drs {
// Following the suggested procedure to enable mesh-wide mTLS, host might be '*.local':
// https://istio.io/docs/tasks/security/authn-policy/#globally-enabling-istio-mutual-tls
host, hostPresent := dr.GetSpec()["host"]
if !hostPresent || host != "*.local" {
continue
}

if trafficPolicy, trafficPresent := dr.GetSpec()["trafficPolicy"]; trafficPresent {
if trafficCasted, ok := trafficPolicy.(map[string]interface{}); ok {
if tls, found := trafficCasted["tls"]; found {
if tlsCasted, ok := tls.(map[string]interface{}); ok {
if mode, found := tlsCasted["mode"]; found {
if modeCasted, ok := mode.(string); ok {
if modeCasted == "ISTIO_MUTUAL" {
mtlsEnabled = true
break
}
}
}
}
}
}
}
}

return mtlsEnabled, nil
}

func (in *IstioConfigService) getAllDestinationRules(namespaces []string) ([]kubernetes.IstioObject, error) {
allDestinationRules := make([]kubernetes.IstioObject, 0)

wg := sync.WaitGroup{}
errChan := make(chan error, 1)

wg.Add(len(namespaces))

for _, namespace := range namespaces {
go func(ns string) {
defer wg.Done()

drs, err := in.k8s.GetDestinationRules(ns, "")
if err != nil {
errChan <- err
return
}

allDestinationRules = append(allDestinationRules, drs...)
}(namespace)
}

wg.Wait()
close(errChan)

for err := range errChan {
if err != nil {
return nil, err
}
}

return allDestinationRules, nil
}
188 changes: 188 additions & 0 deletions business/istio_config_test.go
Expand Up @@ -804,3 +804,191 @@ func TestCreateIstioConfigDetails(t *testing.T) {
assert.Equal("listchecker-to-update", createTemplate.Template.Metadata.Name)
assert.Nil(err)
}

func TestCorrectMeshPolicy(t *testing.T) {
assert := assert.New(t)

k8s := new(kubetest.K8SClientMock)
k8s.On("GetMeshPolicies", "test").Return(fakeMeshPolicyEnablingMTLS("default"), nil)

istioConfigService := IstioConfigService{k8s: k8s}
meshPolicyEnabled, err := (istioConfigService).hasMeshPolicyEnabled([]string{"test"})

assert.NoError(err)
assert.Equal(true, meshPolicyEnabled)
}

func TestPolicyWithWrongName(t *testing.T) {
assert := assert.New(t)

k8s := new(kubetest.K8SClientMock)
k8s.On("GetMeshPolicies", "test").Return(fakeMeshPolicyEnablingMTLS("wrong-name"), nil)

istioConfigService := IstioConfigService{k8s: k8s}
isGloballyEnabled, err := (istioConfigService).hasMeshPolicyEnabled([]string{"test"})

assert.NoError(err)
assert.Equal(false, isGloballyEnabled)
}

func fakeMeshPolicyEnablingMTLS(name string) []kubernetes.IstioObject {
mtls := []interface{}{
map[string]interface{}{
"mtls": "",
},
}

policy := data.CreateEmptyMeshPolicy(name, mtls)

return []kubernetes.IstioObject{policy}
}

func TestWithoutMeshPolicy(t *testing.T) {
assert := assert.New(t)

k8s := new(kubetest.K8SClientMock)
k8s.On("GetMeshPolicies", "test").Return([]kubernetes.IstioObject{}, nil)

istioConfigService := IstioConfigService{k8s: k8s}
meshPolicyEnabled, err := (istioConfigService).hasMeshPolicyEnabled([]string{"test"})

assert.NoError(err)
assert.Equal(false, meshPolicyEnabled)
}

func TestMeshPolicyWithTargets(t *testing.T) {
assert := assert.New(t)

k8s := new(kubetest.K8SClientMock)
k8s.On("GetMeshPolicies", "test").Return(fakeMeshPolicyEnablingMTLSSpecificTarget(), nil)

istioConfigService := IstioConfigService{k8s: k8s}
meshPolicyEnabled, err := (istioConfigService).hasMeshPolicyEnabled([]string{"test"})

assert.NoError(err)
assert.Equal(false, meshPolicyEnabled)
}

func fakeMeshPolicyEnablingMTLSSpecificTarget() []kubernetes.IstioObject {
targets := []interface{}{
map[string]interface{}{
"name": "productpage",
},
}

mtls := []interface{}{
map[string]interface{}{
"mtls": "",
},
}

policy := data.AddTargetsToMeshPolicy(targets,
data.CreateEmptyMeshPolicy("non-global-tls-enabled", mtls))

return []kubernetes.IstioObject{policy}
}

func TestDestinationRuleEnabled(t *testing.T) {
assert := assert.New(t)

dr := data.AddTrafficPolicyToDestinationRule(data.CreateMTLSTrafficPolicyForDestinationRules(),
data.CreateEmptyDestinationRule("istio-system", "default", "*.local"))

k8s := new(kubetest.K8SClientMock)
k8s.On("GetDestinationRules", "test", "").Return([]kubernetes.IstioObject{dr}, nil)

istioConfigService := IstioConfigService{k8s: k8s}
drEnabled, err := (istioConfigService).hasDestinationRuleEnabled([]string{"test"})

assert.NoError(err)
assert.Equal(true, drEnabled)
}

func TestDRWildcardLocalHost(t *testing.T) {
assert := assert.New(t)

dr := data.AddTrafficPolicyToDestinationRule(data.CreateMTLSTrafficPolicyForDestinationRules(),
data.CreateEmptyDestinationRule("myproject", "default", "sleep.foo.svc.cluster.local"))

k8s := new(kubetest.K8SClientMock)
k8s.On("GetDestinationRules", "test", "").Return([]kubernetes.IstioObject{dr}, nil)

istioConfigService := IstioConfigService{k8s: k8s}
drEnabled, err := (istioConfigService).hasDestinationRuleEnabled([]string{"test"})

assert.NoError(err)
assert.Equal(false, drEnabled)
}

func TestDRNotMutualTLSMode(t *testing.T) {
assert := assert.New(t)

trafficPolicy := map[string]interface{}{
"tls": map[string]interface{}{
"mode": "SIMPLE",
},
}

dr := data.AddTrafficPolicyToDestinationRule(trafficPolicy,
data.CreateEmptyDestinationRule("istio-system", "default", "*.local"))

k8s := new(kubetest.K8SClientMock)
k8s.On("GetDestinationRules", "test", "").Return([]kubernetes.IstioObject{dr}, nil)

istioConfigService := IstioConfigService{k8s: k8s}
drEnabled, err := (istioConfigService).hasDestinationRuleEnabled([]string{"test"})

assert.NoError(err)
assert.Equal(false, drEnabled)
}

func TestMeshStatusEnabled(t *testing.T) {
assert := assert.New(t)

dr := data.AddTrafficPolicyToDestinationRule(data.CreateMTLSTrafficPolicyForDestinationRules(),
data.CreateEmptyDestinationRule("istio-system", "default", "*.local"))

k8s := new(kubetest.K8SClientMock)
k8s.On("GetDestinationRules", "test", "").Return([]kubernetes.IstioObject{dr}, nil)
k8s.On("GetMeshPolicies", "test").Return(fakeMeshPolicyEnablingMTLS("default"), nil)

istioConfigService := IstioConfigService{k8s: k8s}
status, err := (istioConfigService).MeshWidemTLSStatus([]string{"test"})

assert.NoError(err)
assert.Equal(MeshmTLSEnabled, status)
}

func TestMeshStatusPartiallyEnabled(t *testing.T) {
assert := assert.New(t)

dr := data.AddTrafficPolicyToDestinationRule(data.CreateMTLSTrafficPolicyForDestinationRules(),
data.CreateEmptyDestinationRule("istio-system", "default", "sleep.foo.svc.cluster.local"))

k8s := new(kubetest.K8SClientMock)
k8s.On("GetDestinationRules", "test", "").Return([]kubernetes.IstioObject{dr}, nil)
k8s.On("GetMeshPolicies", "test").Return(fakeMeshPolicyEnablingMTLS("default"), nil)

istioConfigService := IstioConfigService{k8s: k8s}
status, err := (istioConfigService).MeshWidemTLSStatus([]string{"test"})

assert.NoError(err)
assert.Equal(MeshmTLSPartiallyEnabled, status)
}

func TestMeshStatusNotEnabled(t *testing.T) {
assert := assert.New(t)

dr := data.AddTrafficPolicyToDestinationRule(data.CreateMTLSTrafficPolicyForDestinationRules(),
data.CreateEmptyDestinationRule("istio-system", "default", "sleep.foo.svc.cluster.local"))

k8s := new(kubetest.K8SClientMock)
k8s.On("GetDestinationRules", "test", "").Return([]kubernetes.IstioObject{dr}, nil)
k8s.On("GetMeshPolicies", "test").Return(fakeMeshPolicyEnablingMTLS("wrong-name"), nil)

istioConfigService := IstioConfigService{k8s: k8s}
status, err := (istioConfigService).MeshWidemTLSStatus([]string{"test"})

assert.NoError(err)
assert.Equal(MeshmTLSNotEnabled, status)
}
xeviknal marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions business/namespaces.go
Expand Up @@ -49,12 +49,12 @@ func (in *NamespaceService) GetNamespaces() ([]models.Namespace, error) {
namespaces = models.CastProjectCollection(projects)
}
} else {
services, err := in.k8s.GetNamespaces()
nss, err := in.k8s.GetNamespaces()
if err != nil {
return nil, err
}

namespaces = models.CastNamespaceCollection(services)
namespaces = models.CastNamespaceCollection(nss)
}

result := namespaces
Expand Down
1 change: 1 addition & 0 deletions deploy/kubernetes/clusterrole.yaml
Expand Up @@ -96,6 +96,7 @@ rules:
- apiGroups: ["authentication.istio.io"]
resources:
- policies
- meshpolicies
verbs:
- create
- delete
Expand Down
1 change: 1 addition & 0 deletions deploy/openshift/clusterrole.yaml
Expand Up @@ -96,6 +96,7 @@ rules:
- apiGroups: ["authentication.istio.io"]
resources:
- policies
- meshpolicies
xeviknal marked this conversation as resolved.
Show resolved Hide resolved
verbs:
- create
- delete
Expand Down
1 change: 1 addition & 0 deletions kubernetes/client.go
Expand Up @@ -52,6 +52,7 @@ type IstioClientInterface interface {
GetIstioRule(namespace string, istiorule string) (IstioObject, error)
GetIstioRules(namespace string) ([]IstioObject, error)
GetJobs(namespace string) ([]batch_v1.Job, error)
GetMeshPolicies(namespace string) ([]IstioObject, error)
GetNamespace(namespace string) (*v1.Namespace, error)
GetNamespaces() ([]v1.Namespace, error)
GetPods(namespace, labelSelector string) ([]v1.Pod, error)
Expand Down