[NETCONF-1115] Handle unencrypted password in login-password for topology node Created: 03/Aug/23 Updated: 07/Feb/24 |
|
| Status: | Confirmed |
| Project: | netconf |
| Component/s: | netconf |
| Affects Version/s: | None |
| Fix Version/s: | 7.0.0 |
| Type: | Improvement | Priority: | Medium |
| Reporter: | Sangwook Ha | Assignee: | Ivan Hrasko |
| Resolution: | Unresolved | Votes: | 0 |
| Labels: | pt | ||
| Remaining Estimate: | Not Specified | ||
| Time Spent: | Not Specified | ||
| Original Estimate: | Not Specified | ||
| Issue Links: |
|
||||||||||||||||||||||||||||
| Description |
|
netconf-node-topology:login-password/password is supposed to keep the encrypted password of a NETCONF device. However, in some cases a password saved in field may work even if it's not properly encrypted because AAAEncryptionService.decrypt may return the original password when decryption fails. For example, the following request with unencrypted password PUT /rests/data/network-topology:network-topology/topology=topology-netconf/node=netconf-mdsal
{
"network-topology:node": [
{
"node-id": "netconf-mdsal",
"netconf-node-topology:concurrent-rpc-limit": 0,
"netconf-node-topology:schema-cache-directory": "netconf-mdsal",
"netconf-node-topology:login-password": {
"username": "admin",
"password": "adminadmin"
},
"netconf-node-topology:default-request-timeout-millis": 1800000,
"netconf-node-topology:port": 2830,
"netconf-node-topology:tcp-only": false,
"netconf-node-topology:host": "127.0.0.1",
"netconf-node-topology:actor-response-wait-time": 600,
"netconf-node-topology:keepalive-delay": 600
}
]
}
makes controller attempt to establish a NETCONF session with adminadmin as the password after failing to decrypt the password: 02:03:07.828 ERROR [opendaylight-cluster-data-notification-dispatcher-58] Failed to decrypt encoded data javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher at com.sun.crypto.provider.CipherCore.prepareInputBuffer(CipherCore.java:888) ~[?:?] at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:730) ~[?:?] at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436) ~[?:?] at javax.crypto.Cipher.doFinal(Cipher.java:2205) ~[?:?] at org.opendaylight.aaa.encrypt.impl.AAAEncryptionServiceImpl.decrypt(AAAEncryptionServiceImpl.java:154) ~[?:?] at org.opendaylight.netconf.topology.spi.DefaultNetconfClientConfigurationBuilderFactory.getHandlerFromCredentials(DefaultNetconfClientConfigurationBuilderFactory.java:96) ~[bundleFile:?] at org.opendaylight.netconf.topology.spi.DefaultNetconfClientConfigurationBuilderFactory.createClientConfigurationBuilder(DefaultNetconfClientConfigurationBuilderFactory.java:68) ~[bundleFile:?] at org.opendaylight.netconf.topology.spi.NetconfNodeHandler.<init>(NetconfNodeHandler.java:143) ~[bundleFile:?] at org.opendaylight.netconf.topology.spi.AbstractNetconfTopology.setupConnection(AbstractNetconfTopology.java:142) ~[bundleFile:?] at org.opendaylight.netconf.topology.spi.AbstractNetconfTopology.lockedEnsureNode(AbstractNetconfTopology.java:108) ~[bundleFile:?] at org.opendaylight.netconf.topology.spi.AbstractNetconfTopology.ensureNode(AbstractNetconfTopology.java:96) ~[bundleFile:?] at org.opendaylight.netconf.topology.impl.NetconfTopologyImpl.onDataTreeChanged(NetconfTopologyImpl.java:145) ~[?:?] at org.opendaylight.mdsal.binding.dom.adapter.BindingDOMDataTreeChangeListenerAdapter.onDataTreeChanged(BindingDOMDataTreeChangeListenerAdapter.java:44) ~[bundleFile:?] at org.opendaylight.controller.cluster.datastore.DataTreeChangeListenerActor.dataTreeChanged(DataTreeChangeListenerActor.java:90) ~[bundleFile:?] at org.opendaylight.controller.cluster.datastore.DataTreeChangeListenerActor.handleReceive(DataTreeChangeListenerActor.java:45) ~[bundleFile:?] at akka.japi.pf.UnitCaseStatement.apply(CaseStatements.scala:24) ~[bundleFile:?] at akka.japi.pf.UnitCaseStatement.apply(CaseStatements.scala:20) ~[bundleFile:?] at scala.PartialFunction.applyOrElse(PartialFunction.scala:214) ~[bundleFile:?] at scala.PartialFunction.applyOrElse$(PartialFunction.scala:213) ~[bundleFile:?] at akka.japi.pf.UnitCaseStatement.applyOrElse(CaseStatements.scala:20) ~[bundleFile:?] at scala.PartialFunction$OrElse.applyOrElse(PartialFunction.scala:269) ~[bundleFile:?] at scala.PartialFunction$OrElse.applyOrElse(PartialFunction.scala:270) ~[bundleFile:?] at akka.actor.Actor.aroundReceive(Actor.scala:537) ~[bundleFile:?] at akka.actor.Actor.aroundReceive$(Actor.scala:535) ~[bundleFile:?] at akka.actor.AbstractActor.aroundReceive(AbstractActor.scala:220) ~[bundleFile:?] at akka.actor.ActorCell.receiveMessage(ActorCell.scala:579) ~[bundleFile:?] at akka.actor.ActorCell.invoke(ActorCell.scala:547) ~[bundleFile:?] at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270) ~[bundleFile:?] at akka.dispatch.Mailbox.run(Mailbox.scala:231) ~[bundleFile:?] at akka.dispatch.Mailbox.exec(Mailbox.scala:243) ~[bundleFile:?] at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) ~[?:?] at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] 02:03:07.855 WARN [sshd-NetconfSshClient[2914912]-nio2-thread-3] Server at /127.0.0.1:2830 presented unverified EC key: SHA256:3kA4ClemSJjEhFPu1EdCQnWku+XYuWQi33bFFRK90G8 In this case the connection failed because the password is incorrect. But there are many cases where the correct unencrypted password works. This behavior encourages keeping unencrypted password in the configuration because it may not discovered or it may be simply ignored because it works. Also, this may cause confusion since some may work but some may not. So the device should not be activated when decryption fails to mitigate the issue. |
| Comments |
| Comment by Ruslan Kashapov [ 29/Aug/23 ] |
|
Cause:
Non-encrypted password value as a case of incorrect configuration to be handled via NETCONF-1114. The expected resolution is no connection to device performed if configuration processing fails with immediate failure status to be set for device. Delegating password value encryption to user requires at least encryption service API exposed so user would have the ability to encrypt the password value by himself with assurance this value will be properly decrypted (because same service will use same parameters). However this approach requires shared component update, which complicates and delays solution delivery. The suggested approach is to add dynamic password encryption to direct data update via dedicated DataTreeChangeListener implementation:
|
| Comment by Ivan Hrasko [ 17/Oct/23 ] |
|
We are going from different approach: Enforce base64 encoding for odl-netconf-device model for of all leafs that are claiming its their type by YANG string type pattern. Analyze RPCs which can be used to create such data, realize that we want to allow user to use plain strings in RPC payloads and adapt payloads YANG definition accordingly. If needed to modify odl-netconf-device or netconf-node-topology create new revisions in that case. |
| Comment by Sangwook Ha [ 19/Oct/23 ] |
|
Enforcing base64 encoding will prevent many invalid password values but it's still possible to pass the pattern checking without being actually base64 encoded. It seems problematic that the decrypt function does not have clear API contract that can signal decryption failure, not even documentation, when it's a public API used by other downstream projects - and also inconsistent in that it catches decryption errors but not that of base64 decoding. |
| Comment by Peter Suna [ 24/Oct/23 ] |
|
I will investigate the potential consequences of AAAEncryptionServiceImpl throwing an exception in case of encryption or decryption failure. IMHO, the primary concept is to delegate the validation of password values to the data store, relieving upper layers of this responsibility. |
| Comment by Ivan Hrasko [ 05/Jan/24 ] |
|
The issue is blocked by |