From 92982171260ac2370515d14f5ec362726a3c3434 Mon Sep 17 00:00:00 2001 From: Huamin Chen Date: Fri, 17 Mar 2017 16:42:15 -0400 Subject: [PATCH] Add iSCSI CHAP authentication Signed-off-by: Huamin Chen --- pkg/api/pod/util.go | 4 ++ pkg/api/pod/util_test.go | 7 ++- pkg/api/v1/pod/util.go | 5 +- pkg/api/v1/pod/util_test.go | 7 ++- pkg/volume/iscsi/iscsi.go | 53 ++++++++++++------ pkg/volume/iscsi/iscsi_test.go | 2 +- pkg/volume/iscsi/iscsi_util.go | 98 +++++++++++++++++++++++++++++++--- 7 files changed, 149 insertions(+), 27 deletions(-) diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index 6a7abad016..5ecd6a4705 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -88,6 +88,10 @@ func VisitPodSecretNames(pod *api.Pod, visitor func(string) bool) bool { if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) { return false } + case source.ISCSI != nil: + if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) { + return false + } } } return true diff --git a/pkg/api/pod/util_test.go b/pkg/api/pod/util_test.go index dc850372c6..f3530cb191 100644 --- a/pkg/api/pod/util_test.go +++ b/pkg/api/pod/util_test.go @@ -85,7 +85,11 @@ func TestPodSecrets(t *testing.T) { VolumeSource: api.VolumeSource{ ScaleIO: &api.ScaleIOVolumeSource{ SecretRef: &api.LocalObjectReference{ - Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}}, + Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}, { + VolumeSource: api.VolumeSource{ + ISCSI: &api.ISCSIVolumeSource{ + SecretRef: &api.LocalObjectReference{ + Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}}, }, } extractedNames := sets.NewString() @@ -114,6 +118,7 @@ func TestPodSecrets(t *testing.T) { "Spec.Volumes[*].VolumeSource.Secret", "Spec.Volumes[*].VolumeSource.Secret.SecretName", "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef", + "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef", ) secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&api.Pod{})) secretPaths = secretPaths.Difference(excludedSecretPaths) diff --git a/pkg/api/v1/pod/util.go b/pkg/api/v1/pod/util.go index 517dad2070..099a4356bd 100644 --- a/pkg/api/v1/pod/util.go +++ b/pkg/api/v1/pod/util.go @@ -176,7 +176,10 @@ func VisitPodSecretNames(pod *v1.Pod, visitor func(string) bool) bool { if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) { return false } - + case source.ISCSI != nil: + if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) { + return false + } } } return true diff --git a/pkg/api/v1/pod/util_test.go b/pkg/api/v1/pod/util_test.go index 7227efa72e..4e04f83451 100644 --- a/pkg/api/v1/pod/util_test.go +++ b/pkg/api/v1/pod/util_test.go @@ -253,7 +253,11 @@ func TestPodSecrets(t *testing.T) { VolumeSource: v1.VolumeSource{ ScaleIO: &v1.ScaleIOVolumeSource{ SecretRef: &v1.LocalObjectReference{ - Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}}, + Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}, { + VolumeSource: v1.VolumeSource{ + ISCSI: &v1.ISCSIVolumeSource{ + SecretRef: &v1.LocalObjectReference{ + Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}}, }, } extractedNames := sets.NewString() @@ -282,6 +286,7 @@ func TestPodSecrets(t *testing.T) { "Spec.Volumes[*].VolumeSource.Secret", "Spec.Volumes[*].VolumeSource.Secret.SecretName", "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef", + "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef", ) secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&v1.Pod{})) secretPaths = secretPaths.Difference(excludedSecretPaths) diff --git a/pkg/volume/iscsi/iscsi.go b/pkg/volume/iscsi/iscsi.go index 6ccf90cc71..e7e83e96e0 100644 --- a/pkg/volume/iscsi/iscsi.go +++ b/pkg/volume/iscsi/iscsi.go @@ -99,10 +99,23 @@ func (plugin *iscsiPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *iscsiPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { // Inject real implementations here, test through the internal function. - return plugin.newMounterInternal(spec, pod.UID, &ISCSIUtil{}, plugin.host.GetMounter()) + var secret map[string]string + source, _, err := getVolumeSource(spec) + if err != nil { + return nil, err + } + + if source.SecretRef != nil { + if secret, err = ioutil.GetSecretForPod(pod, source.SecretRef.Name, plugin.host.GetKubeClient()); err != nil { + glog.Errorf("Couldn't get secret from %v/%v", pod.Namespace, source.SecretRef) + return nil, err + } + } + + return plugin.newMounterInternal(spec, pod.UID, &ISCSIUtil{}, plugin.host.GetMounter(), secret) } -func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface) (volume.Mounter, error) { +func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, secret map[string]string) (volume.Mounter, error) { // iscsi volumes used directly in a pod have a ReadOnly flag set by the pod author. // iscsi volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV iscsi, readOnly, err := getVolumeSource(spec) @@ -121,14 +134,17 @@ func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UI return &iscsiDiskMounter{ iscsiDisk: &iscsiDisk{ - podUID: podUID, - volName: spec.Name(), - portals: bkportal, - iqn: iscsi.IQN, - lun: lun, - iface: iface, - manager: manager, - plugin: plugin}, + podUID: podUID, + volName: spec.Name(), + portals: bkportal, + iqn: iscsi.IQN, + lun: lun, + iface: iface, + chap_discovery: iscsi.DiscoveryCHAPAuth, + chap_session: iscsi.SessionCHAPAuth, + secret: secret, + manager: manager, + plugin: plugin}, fsType: iscsi.FSType, readOnly: readOnly, mounter: &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}, @@ -173,13 +189,16 @@ func (plugin *iscsiPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*v } type iscsiDisk struct { - volName string - podUID types.UID - portals []string - iqn string - lun string - iface string - plugin *iscsiPlugin + volName string + podUID types.UID + portals []string + iqn string + lun string + iface string + chap_discovery bool + chap_session bool + secret map[string]string + plugin *iscsiPlugin // Utility interface that provides API calls to the provider to attach/detach disks. manager diskManager volume.MetricsNil diff --git a/pkg/volume/iscsi/iscsi_test.go b/pkg/volume/iscsi/iscsi_test.go index 596a85d3ac..f18b9e33f5 100644 --- a/pkg/volume/iscsi/iscsi_test.go +++ b/pkg/volume/iscsi/iscsi_test.go @@ -141,7 +141,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) { fakeManager := NewFakeDiskManager() defer fakeManager.Cleanup() fakeMounter := &mount.FakeMounter{} - mounter, err := plug.(*iscsiPlugin).newMounterInternal(spec, types.UID("poduid"), fakeManager, fakeMounter) + mounter, err := plug.(*iscsiPlugin).newMounterInternal(spec, types.UID("poduid"), fakeManager, fakeMounter, nil) if err != nil { t.Errorf("Failed to make a new Mounter: %v", err) } diff --git a/pkg/volume/iscsi/iscsi_util.go b/pkg/volume/iscsi/iscsi_util.go index cfb71de46d..5df9cea8d3 100755 --- a/pkg/volume/iscsi/iscsi_util.go +++ b/pkg/volume/iscsi/iscsi_util.go @@ -31,6 +31,59 @@ import ( "k8s.io/kubernetes/pkg/volume" ) +var ( + chap_st = []string{ + "discovery.sendtargets.auth.username", + "discovery.sendtargets.auth.password", + "discovery.sendtargets.auth.username_in", + "discovery.sendtargets.auth.password_in"} + chap_sess = []string{ + "node.session.auth.username", + "node.session.auth.password", + "node.session.auth.username_in", + "node.session.auth.password_in"} +) + +func updateISCSIDiscoverydb(b iscsiDiskMounter, tp string) error { + if b.chap_discovery { + out, err := b.plugin.execCommand("iscsiadm", []string{"-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.iface, "-o", "update", "-n", "discovery.sendtargets.auth.authmethod", "-v", "CHAP"}) + if err != nil { + return fmt.Errorf("iscsi: failed to update discoverydb with CHAP, output: %v", string(out)) + } + + for _, k := range chap_st { + v := b.secret[k] + if len(v) > 0 { + out, err := b.plugin.execCommand("iscsiadm", []string{"-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.iface, "-o", "update", "-n", k, "-v", v}) + if err != nil { + return fmt.Errorf("iscsi: failed to update discoverydb key %q with value %q error: %v", k, v, string(out)) + } + } + } + } + return nil +} + +func updateISCSINode(b iscsiDiskMounter, tp string) error { + if b.chap_session { + out, err := b.plugin.execCommand("iscsiadm", []string{"-m", "node", "-p", tp, "-T", b.iqn, "-I", b.iface, "-o", "update", "-n", "node.session.auth.authmethod", "-v", "CHAP"}) + if err != nil { + return fmt.Errorf("iscsi: failed to update node with CHAP, output: %v", string(out)) + } + + for _, k := range chap_sess { + v := b.secret[k] + if len(v) > 0 { + out, err := b.plugin.execCommand("iscsiadm", []string{"-m", "node", "-p", tp, "-T", b.iqn, "-I", b.iface, "-o", "update", "-n", k, "-v", v}) + if err != nil { + return fmt.Errorf("iscsi: failed to update node session key %q with value %q error: %v", k, v, string(out)) + } + } + } + } + return nil +} + // stat a path, if not exists, retry maxRetries times // when iscsi transports other than default are used, use glob instead as pci id of device is unknown type StatFunc func(string) (os.FileInfo, error) @@ -105,6 +158,7 @@ func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) error { var devicePath string var devicePaths []string var iscsiTransport string + var lastErr error out, err := b.plugin.execCommand("iscsiadm", []string{"-m", "iface", "-I", b.iface, "-o", "show"}) if err != nil { @@ -133,21 +187,41 @@ func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) error { } exist := waitForPathToExist(devicePath, 1, iscsiTransport) if exist == false { - // discover iscsi target - out, err := b.plugin.execCommand("iscsiadm", []string{"-m", "discovery", "-t", "sendtargets", "-p", tp, "-I", b.iface}) + // build discoverydb and discover iscsi target + b.plugin.execCommand("iscsiadm", []string{"-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.iface, "-o", "new"}) + // update discoverydb with CHAP secret + err = updateISCSIDiscoverydb(b, tp) if err != nil { - glog.Errorf("iscsi: failed to sendtargets to portal %s error: %s", tp, string(out)) + lastErr = fmt.Errorf("iscsi: failed to update discoverydb to portal %s error: %v", tp, err) + continue + } + out, err := b.plugin.execCommand("iscsiadm", []string{"-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.iface, "--discover"}) + if err != nil { + // delete discoverydb record + b.plugin.execCommand("iscsiadm", []string{"-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.iface, "-o", "delete"}) + lastErr = fmt.Errorf("iscsi: failed to sendtargets to portal %s output: %s, err %v", tp, string(out), err) + continue + } + err = updateISCSINode(b, tp) + if err != nil { + // failure to update node db is rare. But deleting record will likely impact those who already start using it. + lastErr = fmt.Errorf("iscsi: failed to update iscsi node to portal %s error: %v", tp, err) continue } // login to iscsi target out, err = b.plugin.execCommand("iscsiadm", []string{"-m", "node", "-p", tp, "-T", b.iqn, "-I", b.iface, "--login"}) if err != nil { - glog.Errorf("iscsi: failed to attach disk:Error: %s (%v)", string(out), err) + // delete the node record from database + b.plugin.execCommand("iscsiadm", []string{"-m", "node", "-p", tp, "-I", b.iface, "-T", b.iqn, "-o", "delete"}) + lastErr = fmt.Errorf("iscsi: failed to attach disk: Error: %s (%v)", string(out), err) continue } exist = waitForPathToExist(devicePath, 10, iscsiTransport) if !exist { glog.Errorf("Could not attach disk: Timeout after 10s") + // update last error + lastErr = fmt.Errorf("Could not attach disk: Timeout after 10s") + continue } else { devicePaths = append(devicePaths, devicePath) } @@ -158,8 +232,8 @@ func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) error { } if len(devicePaths) == 0 { - glog.Errorf("iscsi: failed to get any path for iscsi disk") - return errors.New("failed to get any path for iscsi disk") + glog.Errorf("iscsi: failed to get any path for iscsi disk, last err seen:\n%v", lastErr) + return fmt.Errorf("failed to get any path for iscsi disk, last err seen:\n%v", lastErr) } //Make sure we use a valid devicepath to find mpio device. @@ -233,12 +307,24 @@ func (util *ISCSIUtil) DetachDisk(c iscsiDiskUnmounter, mntPath string) error { if err != nil { glog.Errorf("iscsi: failed to detach disk Error: %s", string(out)) } + // Delete the node record + glog.Infof("iscsi: delete node record target %s iqn %s", portal, iqn) + out, err = c.plugin.execCommand("iscsiadm", []string{"-m", "node", "-p", portal, "-T", iqn, "-I", iface, "-o", "delete"}) + if err != nil { + glog.Errorf("iscsi: failed to delete node record Error: %s", string(out)) + } } else { glog.Infof("iscsi: log out target %s iqn %s", portal, iqn) out, err := c.plugin.execCommand("iscsiadm", []string{"-m", "node", "-p", portal, "-T", iqn, "--logout"}) if err != nil { glog.Errorf("iscsi: failed to detach disk Error: %s", string(out)) } + // Delete the node record + glog.Infof("iscsi: delete node record target %s iqn %s", portal, iqn) + out, err = c.plugin.execCommand("iscsiadm", []string{"-m", "node", "-p", portal, "-T", iqn, "-o", "delete"}) + if err != nil { + glog.Errorf("iscsi: failed to delete node record Error: %s", string(out)) + } } } }