Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

public class BaremetalPingPxeResource extends BaremetalPxeResourceBase {
private static final String Name = "BaremetalPingPxeResource";
private static final String SENSITIVE_VALUE_MASK = "*****";
String _storageServer;
String _pingDir;
String _share;
Expand Down Expand Up @@ -157,8 +158,11 @@ protected PreparePxeServerAnswer execute(PreparePxeServerCommand cmd) {
String script =
String.format("python /usr/bin/prepare_tftp_bootfile.py restore %1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s %10$s %11$s", _tftpDir, cmd.getMac(),
_storageServer, _share, _dir, cmd.getTemplate(), _cifsUserName, _cifsPassword, cmd.getIp(), cmd.getNetMask(), cmd.getGateWay());
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) {
return new PreparePxeServerAnswer(cmd, "prepare PING at " + _ip + " failed, command:" + script);
String maskedScript =
String.format("python /usr/bin/prepare_tftp_bootfile.py restore %1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s %10$s %11$s", _tftpDir, cmd.getMac(),
_storageServer, _share, _dir, cmd.getTemplate(), _cifsUserName, SENSITIVE_VALUE_MASK, cmd.getIp(), cmd.getNetMask(), cmd.getGateWay());
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script, maskedScript)) {
return new PreparePxeServerAnswer(cmd, "prepare PING at " + _ip + " failed, command:" + maskedScript);
}
logger.debug("Prepare Ping PXE server successfully");

Expand All @@ -185,8 +189,11 @@ protected Answer execute(PrepareCreateTemplateCommand cmd) {
String script =
String.format("python /usr/bin/prepare_tftp_bootfile.py backup %1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s %10$s %11$s", _tftpDir, cmd.getMac(),
_storageServer, _share, _dir, cmd.getTemplate(), _cifsUserName, _cifsPassword, cmd.getIp(), cmd.getNetMask(), cmd.getGateWay());
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) {
return new Answer(cmd, false, "prepare for creating template failed, command:" + script);
String maskedScript =
String.format("python /usr/bin/prepare_tftp_bootfile.py backup %1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s %10$s %11$s", _tftpDir, cmd.getMac(),
_storageServer, _share, _dir, cmd.getTemplate(), _cifsUserName, SENSITIVE_VALUE_MASK, cmd.getIp(), cmd.getNetMask(), cmd.getGateWay());
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script, maskedScript)) {
return new Answer(cmd, false, "prepare for creating template failed, command:" + maskedScript);
}
logger.debug("Prepare for creating template successfully");

Expand Down Expand Up @@ -219,6 +226,7 @@ private Answer execute(VmDataCommand cmd) {
try {
List<String[]> vmData = cmd.getVmData();
StringBuilder sb = new StringBuilder();
StringBuilder maskedSb = new StringBuilder();
for (String[] data : vmData) {
String folder = data[0];
String file = data[1];
Expand All @@ -231,8 +239,17 @@ private Answer execute(VmDataCommand cmd) {
sb.append(",");
sb.append(contents);
sb.append(";");
maskedSb.append(cmd.getVmIpAddress());
maskedSb.append(",");
maskedSb.append(folder);
maskedSb.append(",");
maskedSb.append(file);
maskedSb.append(",");
maskedSb.append(SENSITIVE_VALUE_MASK);
maskedSb.append(";");
}
String arg = StringUtils.stripEnd(sb.toString(), ";");
String maskedArg = StringUtils.stripEnd(maskedSb.toString(), ";");

sshConnection.connect(null, 60000, 60000);
if (!sshConnection.authenticateWithPassword(_username, _password)) {
Expand All @@ -241,8 +258,9 @@ private Answer execute(VmDataCommand cmd) {
}

String script = String.format("python /usr/bin/baremetal_user_data.py '%s'", arg);
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) {
return new Answer(cmd, false, "Failed to add user data, command:" + script);
String maskedScript = String.format("python /usr/bin/baremetal_user_data.py '%s'", maskedArg);
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script, maskedScript)) {
return new Answer(cmd, false, "Failed to add user data, command:" + maskedScript);
}

return new Answer(cmd, true, "Success");
Expand Down
50 changes: 46 additions & 4 deletions utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,13 @@
}

public static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd, int nTimes) {
return sshExecuteCmd(sshConnection, cmd, null, nTimes);
}

public static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd, String maskedCmd, int nTimes) {
for (int i = 0; i < nTimes; i++) {
try {
final SSHCmdResult result = sshExecuteCmdOneShot(sshConnection, cmd);
final SSHCmdResult result = sshExecuteCmdOneShot(sshConnection, cmd, maskedCmd);
if (result.isSuccess()) {
return true;
}
Expand All @@ -142,9 +146,13 @@
}

public static SSHCmdResult sshExecuteCmdWithResult(com.trilead.ssh2.Connection sshConnection, String cmd, int nTimes) {
return sshExecuteCmdWithResult(sshConnection, cmd, null, nTimes);
}

public static SSHCmdResult sshExecuteCmdWithResult(com.trilead.ssh2.Connection sshConnection, String cmd, String maskedCmd, int nTimes) {
for (int i = 0; i < nTimes; i++) {
try {
final SSHCmdResult result = sshExecuteCmdOneShot(sshConnection, cmd);
final SSHCmdResult result = sshExecuteCmdOneShot(sshConnection, cmd, maskedCmd);
if (result.isSuccess()) {
return result;
}
Expand All @@ -159,12 +167,32 @@
return sshExecuteCmd(sshConnection, cmd, 3);
}

/**
* Same as {@link #sshExecuteCmd(com.trilead.ssh2.Connection, String)}, but takes a
* separate, already-redacted version of {@code cmd} to use for logging. Callers that build
* commands containing secrets (passwords, user-data, keys, etc.) must supply a
* {@code maskedCmd} with those values replaced, since generic log sanitization cannot
* reliably detect arbitrary positional/free-form secrets.
*/
public static boolean sshExecuteCmd(com.trilead.ssh2.Connection sshConnection, String cmd, String maskedCmd) {
return sshExecuteCmd(sshConnection, cmd, maskedCmd, 3);
}

public static SSHCmdResult sshExecuteCmdWithResult(com.trilead.ssh2.Connection sshConnection, String cmd) {
return sshExecuteCmdWithResult(sshConnection, cmd, 3);
}

public static SSHCmdResult sshExecuteCmdWithResult(com.trilead.ssh2.Connection sshConnection, String cmd, String maskedCmd) {
return sshExecuteCmdWithResult(sshConnection, cmd, maskedCmd, 3);
}

public static SSHCmdResult sshExecuteCmdOneShot(com.trilead.ssh2.Connection sshConnection, String cmd) throws SshException {
LOGGER.debug("Executing cmd: " + cmd.split(KeyStoreUtils.KS_FILENAME)[0]);
return sshExecuteCmdOneShot(sshConnection, cmd, null);
}

public static SSHCmdResult sshExecuteCmdOneShot(com.trilead.ssh2.Connection sshConnection, String cmd, String maskedCmd) throws SshException {

Check warning on line 193 in utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "Brain Method" was detected. Refactor it to reduce at least one of the following metrics: LOC from 71 to 64, Complexity from 18 to 14, Nesting Level from 5 to 2, Number of Variables from 17 to 6.

See more on https://sonarcloud.io/project/issues?id=apache_cloudstack&issues=AZ-JoumokIHhbILjIdbv&open=AZ-JoumokIHhbILjIdbv&pullRequest=13668

Check failure on line 193 in utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 35 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=apache_cloudstack&issues=AZ-JoumokIHhbILjIdbs&open=AZ-JoumokIHhbILjIdbs&pullRequest=13668
String cmdForLogging = getCmdForLogging(cmd, maskedCmd);
LOGGER.debug("Executing cmd: " + cmdForLogging);

Check warning on line 195 in utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Format specifiers should be used instead of string concatenation.

See more on https://sonarcloud.io/project/issues?id=apache_cloudstack&issues=AZ-JoumokIHhbILjIdbt&open=AZ-JoumokIHhbILjIdbt&pullRequest=13668

Check warning on line 195 in utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the built-in formatting to construct this argument.

See more on https://sonarcloud.io/project/issues?id=apache_cloudstack&issues=AZ-JoumokIHhbILjIdbq&open=AZ-JoumokIHhbILjIdbq&pullRequest=13668
Session sshSession = null;
try {
sshSession = sshConnection.openSession();
Expand Down Expand Up @@ -227,7 +255,7 @@

final SSHCmdResult result = new SSHCmdResult(-1, sbStdoutResult.toString(), sbStdErrResult.toString());
if (!StringUtils.isAllEmpty(result.getStdOut(), result.getStdErr())) {
LOGGER.debug("SSH command: " + cmd.split(KeyStoreUtils.KS_FILENAME)[0] + "\nSSH command output:" + result.getStdOut().split("-----BEGIN")[0] + "\n" + result.getStdErr());
LOGGER.debug("SSH command: " + cmdForLogging + "\nSSH command output:" + result.getStdOut().split("-----BEGIN")[0] + "\n" + result.getStdErr());

Check warning on line 258 in utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Invoke method(s) only conditionally. Use the built-in formatting to construct this argument.

See more on https://sonarcloud.io/project/issues?id=apache_cloudstack&issues=AZ-JoumokIHhbILjIdbr&open=AZ-JoumokIHhbILjIdbr&pullRequest=13668

Check warning on line 258 in utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Format specifiers should be used instead of string concatenation.

See more on https://sonarcloud.io/project/issues?id=apache_cloudstack&issues=AZ-JoumokIHhbILjIdbu&open=AZ-JoumokIHhbILjIdbu&pullRequest=13668
}

// exit status delivery might get delayed
Expand All @@ -248,4 +276,18 @@
sshSession.close();
}
}

/**
* Returns the version of {@code cmd} that should be logged. When the caller provides an
* already-redacted {@code maskedCmd}, that is used as-is. Otherwise, falls back to the
* legacy heuristic of stripping everything from the first occurrence of the keystore
* filename onwards, which only hides secrets that happen to follow it (e.g. keystore
* setup commands built by {@code LibvirtServerDiscoverer}).
*/
protected static String getCmdForLogging(String cmd, String maskedCmd) {
if (maskedCmd != null) {
return maskedCmd;
}
return cmd.split(KeyStoreUtils.KS_FILENAME)[0];
}
}
47 changes: 47 additions & 0 deletions utils/src/test/java/com/cloud/utils/ssh/SSHCmdHelperTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//

package com.cloud.utils.ssh;

import org.junit.Assert;
import org.junit.Test;

public class SSHCmdHelperTest {

@Test
public void getCmdForLoggingReturnsMaskedCmdWhenProvided() {
String cmd = "python /usr/bin/prepare_tftp_bootfile.py restore tftp mac server share dir template user SuperSecretPassword ip mask gw";
String maskedCmd = "python /usr/bin/prepare_tftp_bootfile.py restore tftp mac server share dir template user ***** ip mask gw";

String result = SSHCmdHelper.getCmdForLogging(cmd, maskedCmd);

Assert.assertEquals(maskedCmd, result);
Assert.assertFalse(result.contains("SuperSecretPassword"));
}

@Test
public void getCmdForLoggingFallsBackToKeystoreSplitWhenNoMaskProvided() {
String cmd = "setup.sh /etc/cloudstack/agent/agent.properties cloud.jks SuperSecretPassword 825";

String result = SSHCmdHelper.getCmdForLogging(cmd, null);

Assert.assertFalse(result.contains("SuperSecretPassword"));
Assert.assertEquals("setup.sh /etc/cloudstack/agent/agent.properties ", result);
}
}
Loading